From 6afb9ece6228e3fed6a1b73b3a1abd3e9ce747ca Mon Sep 17 00:00:00 2001 From: Ivan Chiang Date: Wed, 17 Apr 2024 16:30:21 +0000 Subject: [PATCH] Add the different summary for the app store details When the initiating package is different from the installing package, show the different summary. E.g WebApk Test: atest AppInstallerInfoPreferenceTest Bug: 329140383 Change-Id: Icd5559bce06c059844269d70926b3c0b39589edb --- res/values/strings.xml | 2 + .../settings/applications/AppStoreUtil.java | 48 ++++++++++++++++--- .../app/appinfo/AppInstallerInfoPreference.kt | 40 ++++++++++++---- .../appinfo/AppInstallerInfoPreferenceTest.kt | 28 +++++++++++ 4 files changed, 101 insertions(+), 17 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 90cc39c9fad..cdf506b7940 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4201,6 +4201,8 @@ App details App installed from %1$s + + App installed from %1$s (via %2$s) More info on %1$s diff --git a/src/com/android/settings/applications/AppStoreUtil.java b/src/com/android/settings/applications/AppStoreUtil.java index 636669c7c0a..4c06c47c8f8 100644 --- a/src/com/android/settings/applications/AppStoreUtil.java +++ b/src/com/android/settings/applications/AppStoreUtil.java @@ -22,10 +22,15 @@ import android.content.pm.ApplicationInfo; import android.content.pm.InstallSourceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.text.TextUtils; import android.util.Log; +import android.util.Pair; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Objects; + /** This class provides methods that help dealing with app stores. */ public class AppStoreUtil { private static final String LOG_TAG = "AppStoreUtil"; @@ -37,15 +42,20 @@ public class AppStoreUtil { } /** - * Returns the package name of the app that we consider to be the user-visible 'installer' - * of given packageName, if one is available. + * Returns a {@link Pair pair result}. The first item is the package name of the app that we + * consider to be the user-visible 'installer' of given packageName, if one is available. The + * second item is the {@link InstallSourceInfo install source info} of the given package. */ - @Nullable - public static String getInstallerPackageName(Context context, String packageName) { + @NonNull + public static Pair getInstallerPackageNameAndInstallSourceInfo( + @NonNull Context context, @NonNull String packageName) { + Objects.requireNonNull(context); + Objects.requireNonNull(packageName); + String installerPackageName; + InstallSourceInfo source = null; try { - InstallSourceInfo source = - context.getPackageManager().getInstallSourceInfo(packageName); + source = context.getPackageManager().getInstallSourceInfo(packageName); // By default, use the installing package name. installerPackageName = source.getInstallingPackageName(); // Use the recorded originating package name only if the initiating package is a system @@ -65,7 +75,17 @@ public class AppStoreUtil { Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e); installerPackageName = null; } - return installerPackageName; + return new Pair<>(installerPackageName, source); + } + + /** + * Returns the package name of the app that we consider to be the user-visible 'installer' + * of given packageName, if one is available. + */ + @Nullable + public static String getInstallerPackageName(@NonNull Context context, + @NonNull String packageName) { + return getInstallerPackageNameAndInstallSourceInfo(context, packageName).first; } /** Returns a link to the installer app store for a given package name. */ @@ -88,4 +108,18 @@ public class AppStoreUtil { String installerPackageName = getInstallerPackageName(context, packageName); return getAppStoreLink(context, installerPackageName, packageName); } + + /** + * Returns {@code true} when the initiating package is different from installing package + * for the given {@link InstallSourceInfo install source}. Otherwise, returns {@code false}. + * If the {@code source} is null, also return {@code false}. + */ + public static boolean isInitiatedFromDifferentPackage(@Nullable InstallSourceInfo source) { + if (source == null) { + return false; + } + final String initiatingPackageName = source.getInitiatingPackageName(); + return initiatingPackageName != null + && !TextUtils.equals(source.getInstallingPackageName(), initiatingPackageName); + } } diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt index 62e714a6627..7e160960efa 100644 --- a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt +++ b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt @@ -18,6 +18,8 @@ package com.android.settings.spa.app.appinfo import android.content.Context import android.content.pm.ApplicationInfo +import android.content.pm.InstallSourceInfo +import android.util.Pair import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -51,8 +53,9 @@ fun AppInstallerInfoPreference(app: ApplicationInfo) { if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return val summary by presenter.summaryFlow.collectAsStateWithLifecycle( - initialValue = stringResource(R.string.summary_placeholder), + initialValue = stringResource(R.string.summary_placeholder) ) + val enabled by presenter.enabledFlow.collectAsStateWithLifecycle(initialValue = false) Preference(object : PreferenceModel { override val title = stringResource(R.string.app_install_details_title) @@ -63,16 +66,21 @@ fun AppInstallerInfoPreference(app: ApplicationInfo) { } private class AppInstallerInfoPresenter( - private val context: Context, - private val app: ApplicationInfo, - private val coroutineScope: CoroutineScope, + private val context: Context, + private val app: ApplicationInfo, + private val coroutineScope: CoroutineScope, ) { private val userContext = context.asUser(app.userHandle) private val packageManager = userContext.packageManager + private var installSourceInfo : InstallSourceInfo? = null private val installerPackageFlow = flow { emit(withContext(Dispatchers.IO) { - AppStoreUtil.getInstallerPackageName(userContext, app.packageName) + val result : Pair = + AppStoreUtil.getInstallerPackageNameAndInstallSourceInfo( + userContext, app.packageName) + installSourceInfo = result.second + result.first }) }.sharedFlow() @@ -91,11 +99,23 @@ private class AppInstallerInfoPresenter( } val summaryFlow = installerLabelFlow.map { installerLabel -> - val detailsStringId = when { - app.isInstantApp -> R.string.instant_app_details_summary - else -> R.string.app_install_details_summary + if (app.isInstantApp) { + context.getString(R.string.instant_app_details_summary, installerLabel) + } else if (AppStoreUtil.isInitiatedFromDifferentPackage(installSourceInfo)) { + val initiatingLabel : CharSequence? = Utils.getApplicationLabel( + context, installSourceInfo!!.initiatingPackageName!!) + if (initiatingLabel != null) { + context.getString( + R.string.app_install_details_different_initiating_package_summary, + installerLabel, + initiatingLabel + ) + } else { + context.getString(R.string.app_install_details_summary, installerLabel) + } + } else { + context.getString(R.string.app_install_details_summary, installerLabel) } - context.getString(detailsStringId, installerLabel) } private val intentFlow = installerPackageFlow.map { installerPackage -> @@ -117,5 +137,5 @@ private class AppInstallerInfoPresenter( } private fun Flow.sharedFlow() = - shareIn(coroutineScope, SharingStarted.WhileSubscribed(), 1) + shareIn(coroutineScope, SharingStarted.WhileSubscribed(), 1) } diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt index 31722c93107..96e47076778 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt @@ -19,6 +19,8 @@ package com.android.settings.spa.app.appinfo import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo +import android.content.pm.InstallSourceInfo +import android.util.Pair import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsNotDisplayed @@ -74,10 +76,17 @@ class AppInstallerInfoPreferenceTest { .startMocking() whenever(AppStoreUtil.getInstallerPackageName(any(), eq(PACKAGE_NAME))) .thenReturn(INSTALLER_PACKAGE_NAME) + whenever(AppStoreUtil.getInstallerPackageNameAndInstallSourceInfo(any(), eq(PACKAGE_NAME))) + .thenReturn( + Pair(INSTALLER_PACKAGE_NAME, INSTALL_SOURCE_INFO)) whenever(AppStoreUtil.getAppStoreLink(context, INSTALLER_PACKAGE_NAME, PACKAGE_NAME)) .thenReturn(STORE_LINK) + whenever(AppStoreUtil.isInitiatedFromDifferentPackage(eq(INSTALL_SOURCE_INFO))) + .thenReturn(false) whenever(Utils.getApplicationLabel(context, INSTALLER_PACKAGE_NAME)) .thenReturn(INSTALLER_PACKAGE_LABEL) + whenever(Utils.getApplicationLabel(context, INITIATING_PACKAGE_NAME)) + .thenReturn(INITIATING_PACKAGE_LABEL) whenever(AppUtils.isMainlineModule(any(), eq(PACKAGE_NAME))).thenReturn(false) } @@ -144,6 +153,17 @@ class AppInstallerInfoPreferenceTest { composeTestRule.waitUntilExists(preferenceNode.and(isEnabled())) } + @Test + fun whenNotInstantAppAndDifferentInitiatingPackage() { + whenever(AppStoreUtil.isInitiatedFromDifferentPackage(eq(INSTALL_SOURCE_INFO))) + .thenReturn(true) + setContent() + + composeTestRule.waitUntilExists( + hasText("App installed from installer label (via initiating label)")) + composeTestRule.waitUntilExists(preferenceNode.and(isEnabled())) + } + @Test fun whenClick_startActivity() { setContent() @@ -169,6 +189,14 @@ class AppInstallerInfoPreferenceTest { const val PACKAGE_NAME = "package.name" const val INSTALLER_PACKAGE_NAME = "installer" const val INSTALLER_PACKAGE_LABEL = "installer label" + const val INITIATING_PACKAGE_NAME = "initiating" + const val INITIATING_PACKAGE_LABEL = "initiating label" + val INSTALL_SOURCE_INFO : InstallSourceInfo = InstallSourceInfo( + INITIATING_PACKAGE_NAME, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + INSTALLER_PACKAGE_NAME + ) val STORE_LINK = Intent("store/link") const val UID = 123 val APP = ApplicationInfo().apply {