Merge changes from topic "single-update-owner-dialog" into main
* changes:
[pia v2] Show a user confirmation dialog only when update-ownership isn't changed
Show a user confirmation dialog only when update-ownership isn't changed
[piav2] Cleanup CL to prepare for single user-confirmation dialog change
Cleanup CL to prepare for single user-confirmation dialog change
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index eef21991..c96644c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -23,23 +23,23 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
-
import androidx.annotation.Nullable;
/**
* Installation failed: Return status code to the caller or display failure UI to user
*/
public class InstallFailed extends Activity {
+
private static final String LOG_TAG = InstallFailed.class.getSimpleName();
- /** Label of the app that failed to install */
+ /**
+ * Label of the app that failed to install
+ */
private CharSequence mLabel;
private AlertDialog mDialog;
@@ -80,29 +80,29 @@
setFinishOnTouchOutside(true);
- int statusCode = getIntent().getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
+ Intent intent = getIntent();
+ int statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ boolean returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
- if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
- int legacyStatus = getIntent().getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+ if (returnResult) {
+ int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
// Return result if requested
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
setResult(Activity.RESULT_FIRST_USER, result);
finish();
- } else {
- Intent intent = getIntent();
- ApplicationInfo appInfo = intent
- .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
- Uri packageURI = intent.getData();
+ } else if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
+ // statusCode will be STATUS_FAILURE_ABORTED if the update-owner confirmation dialog was
+ // dismissed by the user. We don't want to show a InstallFailed dialog in this case.
+ // If the user denies install permission for normal installs, this dialog will never be
+ // triggered as the status code is returned from PackageInstallerActivity.java
// Set header icon and title
- PackageUtil.AppSnippet as;
- PackageManager pm = getPackageManager();
- as = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
- PackageUtil.AppSnippet.class);
+ PackageUtil.AppSnippet as = intent.getParcelableExtra(
+ PackageInstallerActivity.EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
// Store label for dialog
mLabel = as.label;
@@ -127,6 +127,8 @@
// Get status messages
setExplanationFromErrorCode(statusCode);
+ } else {
+ finish();
}
}
@@ -135,6 +137,7 @@
* "manage applications" settings page.
*/
public static class OutOfSpaceDialog extends DialogFragment {
+
private InstallFailed mActivity;
@Override
@@ -147,16 +150,16 @@
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(mActivity)
- .setTitle(R.string.out_of_space_dlg_title)
- .setMessage(getString(R.string.out_of_space_dlg_text, mActivity.mLabel))
- .setPositiveButton(R.string.manage_applications, (dialog, which) -> {
- // launch manage applications
- Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
- startActivity(intent);
- mActivity.finish();
- })
- .setNegativeButton(R.string.cancel, (dialog, which) -> mActivity.finish())
- .create();
+ .setTitle(R.string.out_of_space_dlg_title)
+ .setMessage(getString(R.string.out_of_space_dlg_text, mActivity.mLabel))
+ .setPositiveButton(R.string.manage_applications, (dialog, which) -> {
+ // launch manage applications
+ Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
+ startActivity(intent);
+ mActivity.finish();
+ })
+ .setNegativeButton(R.string.cancel, (dialog, which) -> mActivity.finish())
+ .create();
}
@Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1a6c2bb..59a511d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -30,6 +30,8 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@@ -91,8 +93,11 @@
// ContentResolver.SCHEME_FILE
// STAGED_SESSION_ID extra contains an ID of a previously staged install session.
final File sourceFile = new File(mPackageURI.getPath());
- PackageUtil.AppSnippet as = getIntent()
- .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
+
+ // Dialogs displayed while changing update-owner have a blank icon. To fix this,
+ // fetch the appSnippet from the source file again
+ PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
+ getIntent().putExtra(EXTRA_APP_SNIPPET, as);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -244,6 +249,14 @@
super.onDestroy();
}
+ @Override
+ public void finish() {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ super.finish();
+ }
+
/**
* Launch the appropriate finish activity (success or failed) for the installation result.
*
@@ -299,7 +312,11 @@
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
try {
- session.commit(pendingIntent.getIntentSender());
+ // Delay committing the session by 100ms to fix a UI glitch while displaying the
+ // Update-Owner change dialog on top of the Installing dialog
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ session.commit(pendingIntent.getIntentSender());
+ }, 100);
} catch (Exception e) {
Log.e(LOG_TAG, "Cannot install package: ", e);
launchFailure(PackageInstaller.STATUS_FAILURE,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index cf2f85e..13251d8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -165,7 +165,9 @@
if (mStagingTask != null) {
mStagingTask.cancel(true);
}
-
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
super.onDestroy();
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index a4c6ac7..3fea599 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -193,6 +193,7 @@
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
+ nextActivity.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
} else {
Uri packageUri = intent.getData();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 8bed945..e0398aa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -176,11 +176,14 @@
}
private CharSequence getExistingUpdateOwnerLabel() {
+ return getApplicationLabel(getExistingUpdateOwner());
+ }
+
+ private String getExistingUpdateOwner() {
try {
final String packageName = mPkgInfo.packageName;
final InstallSourceInfo sourceInfo = mPm.getInstallSourceInfo(packageName);
- final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName();
- return getApplicationLabel(existingUpdateOwner);
+ return sourceInfo.getUpdateOwnerPackageName();
} catch (NameNotFoundException e) {
return null;
}
@@ -299,6 +302,18 @@
}
private void initiateInstall() {
+ final String existingUpdateOwner = getExistingUpdateOwner();
+ if (mSessionId == SessionInfo.INVALID_ID &&
+ !TextUtils.isEmpty(existingUpdateOwner) &&
+ !TextUtils.equals(existingUpdateOwner, mOriginatingPackage)) {
+ // Since update ownership is being changed, the system will request another
+ // user confirmation shortly. Thus, we don't need to ask the user to confirm
+ // installation here.
+ startInstall();
+ return;
+ }
+
+ // Proceed with user confirmation as we are not changing the update-owner in this install.
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
@@ -465,10 +480,13 @@
@Override
protected void onDestroy() {
- super.onDestroy();
while (!mActiveUnknownSourcesListeners.isEmpty()) {
unregister(mActiveUnknownSourcesListeners.get(0));
}
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ super.onDestroy();
}
private void bindUi() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index f7752ff..d969d1c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -420,25 +420,48 @@
* * If AppOP is granted and user action is required to proceed with install
* * If AppOp grant is to be requested from the user
*/
- fun requestUserConfirmation(): InstallStage {
+ fun requestUserConfirmation(): InstallStage? {
return if (isTrustedSource) {
if (localLogv) {
Log.i(LOG_TAG, "Install allowed")
}
- // Returns InstallUserActionRequired stage if install details could be successfully
- // computed, else it returns InstallAborted.
- generateConfirmationSnippet()
+ maybeDeferUserConfirmation()
} else {
val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) {
// Source app already has appOp granted.
- generateConfirmationSnippet()
+ maybeDeferUserConfirmation()
} else {
unknownSourceStage
}
}
}
+ /**
+ * If the update-owner for the incoming app is being changed, defer confirming with the
+ * user and directly proceed with the install. The system will request another
+ * user confirmation shortly.
+ */
+ private fun maybeDeferUserConfirmation(): InstallStage? {
+ // Returns InstallUserActionRequired stage if install details could be successfully
+ // computed, else it returns InstallAborted.
+ val confirmationSnippet: InstallStage = generateConfirmationSnippet()
+
+ val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!)
+ return if (sessionId == SessionInfo.INVALID_ID &&
+ !TextUtils.isEmpty(existingUpdateOwner) &&
+ !TextUtils.equals(existingUpdateOwner, callingPackage)
+ ) {
+ // Since update ownership is being changed, the system will request another
+ // user confirmation shortly. Thus, we don't need to ask the user to confirm
+ // installation here.
+ initiateInstall()
+ null
+ } else {
+ confirmationSnippet
+ }
+ }
+
private fun generateConfirmationSnippet(): InstallStage {
val packageSource: Any?
val pendingUserActionReason: Int
@@ -639,11 +662,14 @@
}
private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? {
+ return getApplicationLabel(getExistingUpdateOwner(pkgInfo))
+ }
+
+ private fun getExistingUpdateOwner(pkgInfo: PackageInfo): String? {
return try {
val packageName = pkgInfo.packageName
val sourceInfo = packageManager.getInstallSourceInfo(packageName)
- val existingUpdateOwner = sourceInfo.updateOwnerPackageName
- getApplicationLabel(existingUpdateOwner)
+ sourceInfo.updateOwnerPackageName
} catch (e: PackageManager.NameNotFoundException) {
null
}
@@ -861,7 +887,12 @@
}
_installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
} else {
- _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+ if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
+ _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+ } else {
+ _installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR))
+ }
+
}
}
@@ -889,8 +920,8 @@
* When the identity of the install source could not be determined, user can skip checking the
* source and directly proceed with the install.
*/
- fun forcedSkipSourceCheck(): InstallStage {
- return generateConfirmationSnippet()
+ fun forcedSkipSourceCheck(): InstallStage? {
+ return maybeDeferUserConfirmation()
}
val stagingProgress: LiveData<Int>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
index 072fb2d..388e03f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -22,6 +22,7 @@
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.distinctUntilChanged
import com.android.packageinstaller.v2.model.InstallRepository
import com.android.packageinstaller.v2.model.InstallStage
import com.android.packageinstaller.v2.model.InstallStaging
@@ -37,6 +38,19 @@
val currentInstallStage: MutableLiveData<InstallStage>
get() = _currentInstallStage
+ init {
+ // Since installing is an async operation, we may get the install result later in time.
+ // Result of the installation will be set in InstallRepository#installResult.
+ // As such, currentInstallStage will need to add another MutableLiveData as a data source
+ _currentInstallStage.addSource(
+ repository.installResult.distinctUntilChanged()
+ ) { installStage: InstallStage? ->
+ if (installStage != null) {
+ _currentInstallStage.value = installStage
+ }
+ }
+ }
+
fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) {
val stage = repository.performPreInstallChecks(intent, callerInfo)
if (stage.stageCode == InstallStage.STAGE_ABORTED) {
@@ -62,12 +76,16 @@
private fun checkIfAllowedAndInitiateInstall() {
val stage = repository.requestUserConfirmation()
- _currentInstallStage.value = stage
+ if (stage != null) {
+ _currentInstallStage.value = stage
+ }
}
fun forcedSkipSourceCheck() {
val stage = repository.forcedSkipSourceCheck()
- _currentInstallStage.value = stage
+ if (stage != null) {
+ _currentInstallStage.value = stage
+ }
}
fun cleanupInstall() {
@@ -80,15 +98,7 @@
}
fun initiateInstall() {
- // Since installing is an async operation, we will get the install result later in time.
- // Result of the installation will be set in InstallRepository#mInstallResult.
- // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
repository.initiateInstall()
- _currentInstallStage.addSource(repository.installResult) { installStage: InstallStage? ->
- if (installStage != null) {
- _currentInstallStage.value = installStage
- }
- }
}
val stagedSessionId: Int