Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6afb9ece authored by Ivan Chiang's avatar Ivan Chiang
Browse files

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
parent f6f2f613
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -4201,6 +4201,8 @@
    <string name="app_install_details_title">App details</string>
    <!-- Manage applications, individual application info screen, summary for the option which will trigger application info in it's installer [CHAR_LIMIT=50] -->
    <string name="app_install_details_summary">App installed from <xliff:g id="app_store">%1$s</xliff:g></string>
    <!-- Manage applications, individual application info screen, summary for the option which will trigger application info in its installer when the installing package and initiating package are different [CHAR_LIMIT=50] -->
    <string name="app_install_details_different_initiating_package_summary">App installed from <xliff:g id="app_store1">%1$s</xliff:g> (via <xliff:g id="app_store2">%2$s</xliff:g>)</string>
    <!-- Manage applications, individual application info screen, summary for the option which will trigger instant app info in it's installer [CHAR_LIMIT=50] -->
    <string name="instant_app_details_summary">More info on <xliff:g id="app_store">%1$s</xliff:g></string>
+41 −7
Original line number Diff line number Diff line
@@ -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<String, InstallSourceInfo> 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);
    }
}
+30 −10
Original line number Diff line number Diff line
@@ -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)
@@ -69,10 +72,15 @@ private class AppInstallerInfoPresenter(
) {
    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<String, InstallSourceInfo> =
                    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 ->
+28 −0
Original line number Diff line number Diff line
@@ -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<String, InstallSourceInfo>(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 {