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

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

Install app, get result and show appropriate dialog to user

Kick off the install process and wait for the result. Show appropriate dialogs at each stage: installing, install success, install failure.

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

Change-Id: I172a4a46e0b4190a6352c433c4cf09251bbb89db
parent 5b537f68
Loading
Loading
Loading
Loading
+102 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameFo
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.isPermissionGranted;
import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_DONE;
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.DLG_PACKAGE_ERROR;
@@ -34,6 +35,7 @@ import static com.android.packageinstaller.v2.model.installstagedata.InstallUser
import android.Manifest;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -59,11 +61,15 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
import com.android.packageinstaller.R;
import com.android.packageinstaller.v2.model.EventResultPersister.OutOfIdsException;
import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
import com.android.packageinstaller.v2.model.installstagedata.InstallReady;
import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
import java.io.File;
import java.io.IOException;
@@ -71,6 +77,8 @@ import java.io.IOException;
public class InstallRepository {

    private static final String SCHEME_PACKAGE = "package";
    private static final String BROADCAST_ACTION =
        "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
    private static final String TAG = InstallRepository.class.getSimpleName();
    private final Context mContext;
    private final PackageManager mPackageManager;
@@ -79,6 +87,7 @@ public class InstallRepository {
    private final DevicePolicyManager mDevicePolicyManager;
    private final AppOpsManager mAppOpsManager;
    private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
    private final MutableLiveData<InstallStage> mInstallResult = new MutableLiveData<>();
    private final boolean mLocalLOGV = false;
    private Intent mIntent;
    private boolean mIsSessionInstall;
@@ -713,6 +722,99 @@ public class InstallRepository {
        }
    }


    /**
     * Kick off the installation. Register a broadcast listener to get the result of the
     * installation and commit the staged session here. If the installation was session based,
     * signal the PackageInstaller that the user has granted permission to proceed with the install
     */
    public void initiateInstall() {
        if (mSessionId > 0) {
            mPackageInstaller.setPermissionsResult(mSessionId, true);
            mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_DONE)
                .setActivityResultCode(Activity.RESULT_OK).build());
            return;
        }

        Uri uri = mIntent.getData();
        if (uri != null && SCHEME_PACKAGE.equals(uri.getScheme())) {
            try {
                mPackageManager.installExistingPackage(mNewPackageInfo.packageName);
                setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null, -1);
            } catch (PackageManager.NameNotFoundException e) {
                setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
                    PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
            }
            return;
        }

        if (mStagedSessionId <= 0) {
            // How did we even land here?
            Log.e(TAG, "Invalid local session and caller initiated session");
            mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
                .build());
            return;
        }

        int installId;
        try {
            mInstallResult.setValue(new InstallInstalling(mAppSnippet));
            installId = InstallEventReceiver.addObserver(mContext,
                EventResultPersister.GENERATE_NEW_ID, this::setStageBasedOnResult);
        } catch (OutOfIdsException e) {
            setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
                PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
            return;
        }

        Intent broadcastIntent = new Intent(BROADCAST_ACTION);
        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        broadcastIntent.setPackage(mContext.getPackageName());
        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(
            mContext, installId, broadcastIntent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);

        try {
            PackageInstaller.Session session = mPackageInstaller.openSession(mStagedSessionId);
            session.commit(pendingIntent.getIntentSender());
        } catch (Exception e) {
            Log.e(TAG, "Session " + mStagedSessionId + " could not be opened.", e);
            mPackageInstaller.abandonSession(mStagedSessionId);
            setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
                PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
        }
    }

    private void setStageBasedOnResult(int statusCode, int legacyStatus, String message,
        int serviceId) {
        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
            boolean shouldReturnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);

            InstallSuccess.Builder successBuilder = new InstallSuccess.Builder(mAppSnippet)
                .setShouldReturnResult(shouldReturnResult);
            Intent resultIntent;
            if (shouldReturnResult) {
                resultIntent = new Intent()
                    .putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED);
            } else {
                resultIntent = mPackageManager
                    .getLaunchIntentForPackage(mNewPackageInfo.packageName);
            }
            successBuilder.setResultIntent(resultIntent);

            mInstallResult.setValue(successBuilder.build());
        } else {
            mInstallResult.setValue(
                new InstallFailed(mAppSnippet, statusCode, legacyStatus, message));
        }
    }

    public MutableLiveData<InstallStage> getInstallResult() {
        return mInstallResult;
    }

    /**
     * Cleanup the staged session. Also signal the packageinstaller that an install session is to
     * be aborted
+1 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ public class InstallAborted extends InstallStage {

    public static final int ABORT_REASON_INTERNAL_ERROR = 0;
    public static final int ABORT_REASON_POLICY = 1;
    public static final int ABORT_REASON_DONE = 2;
    public static final int DLG_PACKAGE_ERROR = 1;
    private final int mStage = InstallStage.STAGE_ABORTED;
    private final int mAbortReason;
+69 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.packageinstaller.v2.model.installstagedata;

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

public class InstallFailed extends InstallStage {

    private final int mStage = InstallStage.STAGE_FAILED;
    @NonNull
    private final AppSnippet mAppSnippet;
    private final int mStatusCode;
    private final int mLegacyCode;
    @Nullable
    private final String mMessage;

    public InstallFailed(@NonNull AppSnippet appSnippet, int statusCode, int legacyCode,
        @Nullable String message) {
        mAppSnippet = appSnippet;
        mLegacyCode = statusCode;
        mStatusCode = legacyCode;
        mMessage = message;
    }

    @Override
    public int getStageCode() {
        return mStage;
    }

    @NonNull
    public Drawable getAppIcon() {
        return mAppSnippet.getIcon();
    }

    @NonNull
    public String getAppLabel() {
        return (String) mAppSnippet.getLabel();
    }

    public int getStatusCode() {
        return mStatusCode;
    }

    public int getLegacyCode() {
        return mLegacyCode;
    }

    @Nullable
    public String getMessage() {
        return mMessage;
    }
}
+47 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.packageinstaller.v2.model.installstagedata;

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

public class InstallInstalling extends InstallStage {

    private final int mStage = InstallStage.STAGE_INSTALLING;
    @NonNull
    private final AppSnippet mAppSnippet;

    public InstallInstalling(@NonNull AppSnippet appSnippet) {
        mAppSnippet = appSnippet;
    }

    @Override
    public int getStageCode() {
        return mStage;
    }

    @NonNull
    public Drawable getAppIcon() {
        return mAppSnippet.getIcon();
    }

    @NonNull
    public String getAppLabel() {
        return (String) mAppSnippet.getLabel();
    }
}
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.packageinstaller.v2.model.installstagedata;

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

public class InstallSuccess extends InstallStage {

    private final int mStage = InstallStage.STAGE_SUCCESS;

    @NonNull
    private final AppSnippet mAppSnippet;
    private final boolean mShouldReturnResult;
    /**
     * <p>If the caller is requesting a result back, this will hold the Intent with
     * EXTRA_INSTALL_RESULT set to INSTALL_SUCCEEDED which is sent back to the caller.</p>
     * <p>If the caller doesn't want the result back, this will hold the Intent that launches
     * the newly installed / updated app.</p>
     */
    @NonNull
    private final Intent mResultIntent;

    public InstallSuccess(@NonNull AppSnippet appSnippet, boolean shouldReturnResult,
        @NonNull Intent launcherIntent) {
        mAppSnippet = appSnippet;
        mShouldReturnResult = shouldReturnResult;
        mResultIntent = launcherIntent;
    }

    @Override
    public int getStageCode() {
        return mStage;
    }

    @NonNull
    public Drawable getAppIcon() {
        return mAppSnippet.getIcon();
    }

    @NonNull
    public String getAppLabel() {
        return (String) mAppSnippet.getLabel();
    }

    public boolean shouldReturnResult() {
        return mShouldReturnResult;
    }

    @NonNull
    public Intent getResultIntent() {
        return mResultIntent;
    }

    public static class Builder {

        private final AppSnippet mAppSnippet;
        private boolean mShouldReturnResult;
        private Intent mLauncherIntent;

        public Builder(@NonNull AppSnippet appSnippet) {
            mAppSnippet = appSnippet;
        }

        public Builder setShouldReturnResult(boolean returnResult) {
            mShouldReturnResult = returnResult;
            return this;
        }

        public Builder setResultIntent(@NonNull Intent intent) {
            mLauncherIntent = intent;
            return this;
        }

        public InstallSuccess build() {
            return new InstallSuccess(mAppSnippet, mShouldReturnResult, mLauncherIntent);
        }
    }
}
Loading