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

Commit 95562303 authored by Sumedh Sen's avatar Sumedh Sen
Browse files

Handle install from unknown sources

If the source app is unknown (i.e. not holding privileged permissions to install) take a detour and ask the user to grant the app, permission to install other apps. We launch the Settings of the source app where users can toggle the permission to install apps.

When the identity of the source cannot be determined, show appropriate warning to the user before proceeding.

Bug: 182205982
Test: builds successfully
Test: No CTS Tests. Flag to use new app is turned off by default

Change-Id: I1e94e0b5c751bd0e5978120ffae04d0d215d600c
parent d810159d
Loading
Loading
Loading
Loading
+104 −5
Original line number Original line Diff line number Diff line
@@ -19,17 +19,21 @@ package com.android.packageinstaller.v2.model;
import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery;
import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery;
import static com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo;
import static com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo;
import static com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet;
import static com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet;
import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner;
import static com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo;
import static com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo;
import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner;
import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested;
import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested;
import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.DLG_PACKAGE_ERROR;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.DLG_PACKAGE_ERROR;
import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE;
import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION;
import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION;
import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE;


import android.Manifest;
import android.Manifest;
import android.app.Activity;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
@@ -73,6 +77,7 @@ public class InstallRepository {
    private final PackageInstaller mPackageInstaller;
    private final PackageInstaller mPackageInstaller;
    private final UserManager mUserManager;
    private final UserManager mUserManager;
    private final DevicePolicyManager mDevicePolicyManager;
    private final DevicePolicyManager mDevicePolicyManager;
    private final AppOpsManager mAppOpsManager;
    private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
    private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
    private final boolean mLocalLOGV = false;
    private final boolean mLocalLOGV = false;
    private Intent mIntent;
    private Intent mIntent;
@@ -89,6 +94,7 @@ public class InstallRepository {
    private int mCallingUid;
    private int mCallingUid;
    private String mCallingPackage;
    private String mCallingPackage;
    private SessionStager mSessionStager;
    private SessionStager mSessionStager;
    private AppOpRequestInfo mAppOpRequestInfo;
    private AppSnippet mAppSnippet;
    private AppSnippet mAppSnippet;
    /**
    /**
     * PackageInfo of the app being installed on device.
     * PackageInfo of the app being installed on device.
@@ -101,6 +107,7 @@ public class InstallRepository {
        mPackageInstaller = mPackageManager.getPackageInstaller();
        mPackageInstaller = mPackageManager.getPackageInstaller();
        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
        mUserManager = context.getSystemService(UserManager.class);
        mUserManager = context.getSystemService(UserManager.class);
        mAppOpsManager = context.getSystemService(AppOpsManager.class);
    }
    }


    /**
    /**
@@ -143,6 +150,9 @@ public class InstallRepository {
        final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage);
        final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage);
        // Uid of the source package, with a preference to uid from ApplicationInfo
        // Uid of the source package, with a preference to uid from ApplicationInfo
        final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid;
        final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid;
        mAppOpRequestInfo = new AppOpRequestInfo(
            getPackageNameForUid(mContext, originatingUid, mCallingPackage),
            originatingUid, callingAttributionTag);


        if (mCallingUid == Process.INVALID_UID && sourceInfo == null) {
        if (mCallingUid == Process.INVALID_UID && sourceInfo == null) {
            // Caller's identity could not be determined. Abort the install
            // Caller's identity could not be determined. Abort the install
@@ -389,10 +399,8 @@ public class InstallRepository {
            // computed, else it returns InstallAborted.
            // computed, else it returns InstallAborted.
            return generateConfirmationSnippet();
            return generateConfirmationSnippet();
        } else {
        } else {
            // This will be uncommented in subsequent changes.
            InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
            // InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
            if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
            InstallStage unknownSourceStage = null;
            if (unknownSourceStage == null) {
                // Source app already has appOp granted.
                // Source app already has appOp granted.
                return generateConfirmationSnippet();
                return generateConfirmationSnippet();
            } else {
            } else {
@@ -633,6 +641,72 @@ public class InstallRepository {
        return true;
        return true;
    }
    }


    private InstallStage handleUnknownSources(AppOpRequestInfo requestInfo) {
        if (requestInfo.getCallingPackage() == null) {
            Log.i(TAG, "No source found for package " + mNewPackageInfo.packageName);
            return new InstallUserActionRequired.Builder(
                USER_ACTION_REASON_ANONYMOUS_SOURCE, null)
                .build();
        }
        // Shouldn't use static constant directly, see b/65534401.
        final String appOpStr =
            AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr,
            requestInfo.getOriginatingUid(),
            requestInfo.getCallingPackage(), requestInfo.getAttributionTag(),
            "Started package installation activity");

        if (mLocalLOGV) {
            Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
        }
        switch (appOpMode) {
            case AppOpsManager.MODE_DEFAULT:
                mAppOpsManager.setMode(appOpStr, requestInfo.getOriginatingUid(),
                    requestInfo.getCallingPackage(), AppOpsManager.MODE_ERRORED);
                // fall through
            case AppOpsManager.MODE_ERRORED:
                try {
                    ApplicationInfo sourceInfo =
                        mPackageManager.getApplicationInfo(requestInfo.getCallingPackage(), 0);
                    AppSnippet sourceAppSnippet = getAppSnippet(mContext, sourceInfo);
                    return new InstallUserActionRequired.Builder(
                        USER_ACTION_REASON_UNKNOWN_SOURCE, sourceAppSnippet)
                        .setDialogMessage(requestInfo.getCallingPackage())
                        .build();
                } catch (NameNotFoundException e) {
                    Log.e(TAG, "Did not find appInfo for " + requestInfo.getCallingPackage());
                    return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
                }
            case AppOpsManager.MODE_ALLOWED:
                return new InstallReady();
            default:
                Log.e(TAG, "Invalid app op mode " + appOpMode
                    + " for OP_REQUEST_INSTALL_PACKAGES found for uid "
                    + requestInfo.getOriginatingUid());
                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
        }
    }

    /**
     * Cleanup the staged session. Also signal the packageinstaller that an install session is to
     * be aborted
     */
    public void cleanupInstall() {
        if (mSessionId > 0) {
            mPackageInstaller.setPermissionsResult(mSessionId, false);
        } else if (mStagedSessionId > 0) {
            cleanupStagingSession();
        }
    }

    /**
     * When the identity of the install source could not be determined, user can skip checking the
     * source and directly proceed with the install.
     */
    public InstallStage forcedSkipSourceCheck() {
        return generateConfirmationSnippet();
    }

    public MutableLiveData<Integer> getStagingProgress() {
    public MutableLiveData<Integer> getStagingProgress() {
        if (mSessionStager != null) {
        if (mSessionStager != null) {
            return mSessionStager.getProgress();
            return mSessionStager.getProgress();
@@ -669,4 +743,29 @@ public class InstallRepository {
            return mUid;
            return mUid;
        }
        }
    }
    }

    public static class AppOpRequestInfo {

        private String mCallingPackage;
        private String mAttributionTag;
        private int mOrginatingUid;

        public AppOpRequestInfo(String callingPackage, int orginatingUid, String attributionTag) {
            mCallingPackage = callingPackage;
            mOrginatingUid = orginatingUid;
            mAttributionTag = attributionTag;
        }

        public String getCallingPackage() {
            return mCallingPackage;
        }

        public String getAttributionTag() {
            return mAttributionTag;
        }

        public int getOriginatingUid() {
            return mOrginatingUid;
        }
    }
}
}
+34 −0
Original line number Original line Diff line number Diff line
@@ -352,6 +352,40 @@ public class PackageUtil {
        return null;
        return null;
    }
    }


    /**
     * @return the packageName corresponding to a UID.
     */
    public static String getPackageNameForUid(Context context, int sourceUid,
        String callingPackage) {
        if (sourceUid == Process.INVALID_UID) {
            return null;
        }
        // If the sourceUid belongs to the system downloads provider, we explicitly return the
        // name of the Download Manager package. This is because its UID is shared with multiple
        // packages, resulting in uncertainty about which package will end up first in the list
        // of packages associated with this UID
        PackageManager pm = context.getPackageManager();
        ApplicationInfo systemDownloadProviderInfo = getSystemDownloadsProviderInfo(
            pm, sourceUid);
        if (systemDownloadProviderInfo != null) {
            return systemDownloadProviderInfo.packageName;
        }
        String[] packagesForUid = pm.getPackagesForUid(sourceUid);
        if (packagesForUid == null) {
            return null;
        }
        if (packagesForUid.length > 1) {
            if (callingPackage != null) {
                for (String packageName : packagesForUid) {
                    if (packageName.equals(callingPackage)) {
                        return packageName;
                    }
                }
            }
            Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
        }
        return packagesForUid[0];
    }


    /**
    /**
     * Utility method to get package information for a given {@link File}
     * Utility method to get package information for a given {@link File}
+11 −8
Original line number Original line Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.packageinstaller.v2.model.installstagedata;
package com.android.packageinstaller.v2.model.installstagedata;


import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Nullable;
import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;


@@ -28,13 +27,13 @@ public class InstallUserActionRequired extends InstallStage {
    public static final int USER_ACTION_REASON_INSTALL_CONFIRMATION = 2;
    public static final int USER_ACTION_REASON_INSTALL_CONFIRMATION = 2;
    private final int mStage = InstallStage.STAGE_USER_ACTION_REQUIRED;
    private final int mStage = InstallStage.STAGE_USER_ACTION_REQUIRED;
    private final int mActionReason;
    private final int mActionReason;
    @NonNull
    @Nullable
    private final AppSnippet mAppSnippet;
    private final AppSnippet mAppSnippet;
    private final boolean mIsAppUpdating;
    private final boolean mIsAppUpdating;
    @Nullable
    @Nullable
    private final String mDialogMessage;
    private final String mDialogMessage;


    public InstallUserActionRequired(int actionReason, @NonNull AppSnippet appSnippet,
    public InstallUserActionRequired(int actionReason, @Nullable AppSnippet appSnippet,
        boolean isUpdating, @Nullable String dialogMessage) {
        boolean isUpdating, @Nullable String dialogMessage) {
        mActionReason = actionReason;
        mActionReason = actionReason;
        mAppSnippet = appSnippet;
        mAppSnippet = appSnippet;
@@ -47,14 +46,14 @@ public class InstallUserActionRequired extends InstallStage {
        return mStage;
        return mStage;
    }
    }


    @NonNull
    @Nullable
    public Drawable getAppIcon() {
    public Drawable getAppIcon() {
        return mAppSnippet.getIcon();
        return mAppSnippet != null ? mAppSnippet.getIcon() : null;
    }
    }


    @NonNull
    @Nullable
    public String getAppLabel() {
    public String getAppLabel() {
        return (String) mAppSnippet.getLabel();
        return mAppSnippet != null ? (String) mAppSnippet.getLabel() : null;
    }
    }


    public boolean isAppUpdating() {
    public boolean isAppUpdating() {
@@ -66,6 +65,10 @@ public class InstallUserActionRequired extends InstallStage {
        return mDialogMessage;
        return mDialogMessage;
    }
    }


    public int getActionReason() {
        return mActionReason;
    }

    public static class Builder {
    public static class Builder {


        private final int mActionReason;
        private final int mActionReason;
@@ -73,7 +76,7 @@ public class InstallUserActionRequired extends InstallStage {
        private boolean mIsAppUpdating;
        private boolean mIsAppUpdating;
        private String mDialogMessage;
        private String mDialogMessage;


        public Builder(int actionReason, @NonNull AppSnippet appSnippet) {
        public Builder(int actionReason, @Nullable AppSnippet appSnippet) {
            mActionReason = actionReason;
            mActionReason = actionReason;
            mAppSnippet = appSnippet;
            mAppSnippet = appSnippet;
        }
        }
+5 −0
Original line number Original line Diff line number Diff line
@@ -23,6 +23,11 @@ public interface InstallActionListener {
     */
     */
    void onPositiveResponse(int stageCode);
    void onPositiveResponse(int stageCode);


    /**
     * Method to dispatch intent for toggling "install from unknown sources" setting for a package
     */
    void sendUnknownAppsIntent(String packageName);

    /**
    /**
     * Method to handle a negative response from the user
     * Method to handle a negative response from the user
     */
     */
+45 −4
Original line number Original line Diff line number Diff line
@@ -16,13 +16,18 @@


package com.android.packageinstaller.v2.ui;
package com.android.packageinstaller.v2.ui;


import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
import static android.os.Process.INVALID_UID;
import static android.os.Process.INVALID_UID;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;


import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Bundle;
import android.os.UserManager;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.util.Log;
import android.view.Window;
import android.view.Window;
import androidx.annotation.Nullable;
import androidx.annotation.Nullable;
@@ -36,6 +41,8 @@ import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment;
import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment;
import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment;
import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment;
import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment;
import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment;
import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment;
import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment;
@@ -50,6 +57,7 @@ public class InstallLaunch extends FragmentActivity implements InstallActionList
            InstallLaunch.class.getPackageName() + ".callingPkgName";
            InstallLaunch.class.getPackageName() + ".callingPkgName";
    private static final String TAG = InstallLaunch.class.getSimpleName();
    private static final String TAG = InstallLaunch.class.getSimpleName();
    private static final String TAG_DIALOG = "dialog";
    private static final String TAG_DIALOG = "dialog";
    private final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
    private final boolean mLocalLOGV = false;
    private final boolean mLocalLOGV = false;
    private InstallViewModel mInstallViewModel;
    private InstallViewModel mInstallViewModel;
    private InstallRepository mInstallRepository;
    private InstallRepository mInstallRepository;
@@ -95,8 +103,20 @@ public class InstallLaunch extends FragmentActivity implements InstallActionList
            }
            }
        } else if (installStage.getStageCode() == InstallStage.STAGE_USER_ACTION_REQUIRED) {
        } else if (installStage.getStageCode() == InstallStage.STAGE_USER_ACTION_REQUIRED) {
            InstallUserActionRequired uar = (InstallUserActionRequired) installStage;
            InstallUserActionRequired uar = (InstallUserActionRequired) installStage;
            switch (uar.getActionReason()) {
                case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION:
                    InstallConfirmationFragment actionDialog = new InstallConfirmationFragment(uar);
                    InstallConfirmationFragment actionDialog = new InstallConfirmationFragment(uar);
                    showDialogInner(actionDialog);
                    showDialogInner(actionDialog);
                    break;
                case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE:
                    ExternalSourcesBlockedFragment externalSourceDialog =
                        new ExternalSourcesBlockedFragment(uar);
                    showDialogInner(externalSourceDialog);
                    break;
                case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE:
                    AnonymousSourceFragment anonymousSourceDialog = new AnonymousSourceFragment();
                    showDialogInner(anonymousSourceDialog);
            }
        } else {
        } else {
            Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
            Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
            showDialogInner(null);
            showDialogInner(null);
@@ -178,11 +198,32 @@ public class InstallLaunch extends FragmentActivity implements InstallActionList


    @Override
    @Override
    public void onPositiveResponse(int reasonCode) {
    public void onPositiveResponse(int reasonCode) {
        // TODO: Implement this
        if (reasonCode == InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE) {
            mInstallViewModel.forcedSkipSourceCheck();
        }
    }
    }


    @Override
    @Override
    public void onNegativeResponse(int stageCode) {
    public void onNegativeResponse(int stageCode) {
        // TODO: Implement this
        if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
            mInstallViewModel.cleanupInstall();
        }
        setResult(Activity.RESULT_CANCELED, true);
    }

    @Override
    public void sendUnknownAppsIntent(String sourcePackageName) {
        Intent settingsIntent = new Intent();
        settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
        final Uri packageUri = Uri.parse("package:" + sourcePackageName);
        settingsIntent.setData(packageUri);
        settingsIntent.setFlags(FLAG_ACTIVITY_NO_HISTORY);

        try {
            startActivityForResult(settingsIntent, REQUEST_TRUST_EXTERNAL_SOURCE);
        } catch (ActivityNotFoundException exc) {
            Log.e(TAG, "Settings activity not found for action: "
                + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
        }
    }
    }
}
}
Loading