Checkbox for removing contributes files

Apps might have contributed files. During uninstall the files are
usually left on the system. To avoid filling up the storage we allow the
user to delete the files during uninstall.

Bug: 112002130
Test: Uninstalled an app that contributed files
Change-Id: I7e71ed524055bdda91ce9e66f995540363ceb229
This commit is contained in:
Philip P. Moltmann
2018-12-10 17:02:35 -08:00
parent b4815ebf76
commit 38ea50c63e
8 changed files with 131 additions and 9 deletions

View File

@@ -141,6 +141,7 @@ applications that come with the platform
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.CLEAR_APP_USER_DATA"/>
</privapp-permissions>
<privapp-permissions package="com.android.permissioncontroller">

View File

@@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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.
-->
<!-- Check box that is displayed in the activity resolver UI for the user
to make their selection the preferred activity. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?android:attr/alertDialogTheme"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingStart="?android:attr/dialogPreferredPadding"
android:paddingEnd="?android:attr/dialogPreferredPadding"
android:clipToPadding="false">
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@android:style/TextAppearance.Material.Subhead" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="-8dp"
android:paddingLeft="8sp"
style="@android:style/TextAppearance.Material.Subhead" />
</LinearLayout>

View File

@@ -119,6 +119,8 @@
<string name="uninstall_update_text">Replace this app with the factory version? All data will be removed.</string>
<!-- [CHAR LIMIT=none] -->
<string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string>
<!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] -->
<string name="uninstall_remove_contributed_files">Also remove <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of associated media files.</string>
<!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] -->
<string name="uninstalling_notification_channel">Running uninstalls</string>

View File

@@ -50,6 +50,8 @@ public class UninstallUninstalling extends Activity implements
"com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
static final String EXTRA_CLEAR_CONTRIBUTED_FILES =
"com.android.packageinstaller.extra.CLEAR_CONTRIBUTED_FILES";
private int mUninstallId;
private ApplicationInfo mAppInfo;
@@ -72,6 +74,8 @@ public class UninstallUninstalling extends Activity implements
if (savedInstanceState == null) {
boolean allUsers = getIntent().getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
false);
boolean clearContributedFiles = getIntent().getBooleanExtra(
EXTRA_CLEAR_CONTRIBUTED_FILES, false);
UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
// Show dialog, which is the whole UI
@@ -95,12 +99,15 @@ public class UninstallUninstalling extends Activity implements
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId,
broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0;
try {
ActivityThread.getPackageManager().getPackageInstaller().uninstall(
new VersionedPackage(mAppInfo.packageName,
PackageManager.VERSION_CODE_HIGHEST),
getPackageName(), allUsers ? PackageManager.DELETE_ALL_USERS : 0,
pendingIntent.getIntentSender(), user.getIdentifier());
getPackageName(), flags, pendingIntent.getIntentSender(),
user.getIdentifier());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}

View File

@@ -285,7 +285,7 @@ public class UninstallerActivity extends Activity {
fragment.show(ft, "dialog");
}
public void startUninstallProgress() {
public void startUninstallProgress(boolean clearContributedFiles) {
boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager());
@@ -310,6 +310,8 @@ public class UninstallerActivity extends Activity {
newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
newIntent.putExtra(UninstallUninstalling.EXTRA_CLEAR_CONTRIBUTED_FILES,
clearContributedFiles);
newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
if (returnResult) {
@@ -358,12 +360,14 @@ public class UninstallerActivity extends Activity {
try {
Log.i(TAG, "Uninstalling extras=" + broadcastIntent.getExtras());
int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0;
ActivityThread.getPackageManager().getPackageInstaller().uninstall(
new VersionedPackage(mDialogInfo.appInfo.packageName,
PackageManager.VERSION_CODE_HIGHEST),
getPackageName(), mDialogInfo.allUsers
? PackageManager.DELETE_ALL_USERS : 0,
pendingIntent.getIntentSender(), mDialogInfo.user.getIdentifier());
getPackageName(), flags, pendingIntent.getIntentSender(),
mDialogInfo.user.getIdentifier());
} catch (Exception e) {
notificationManager.cancel(uninstallId);

View File

@@ -16,6 +16,9 @@
package com.android.packageinstaller.handheld;
import static android.text.format.Formatter.formatFileSize;
import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -24,14 +27,41 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import com.android.packageinstaller.R;
import com.android.packageinstaller.UninstallerActivity;
import java.io.IOException;
import java.util.List;
public class UninstallAlertDialogFragment extends DialogFragment implements
DialogInterface.OnClickListener {
private CheckBox mClearContributedFiles;
/**
* Get number of bytes of the combined files contributed by the package.
*
* @param pkg The package that might have contibuted files.
* @param user The user the package belongs to.
*
* @return The number of bytes.
*/
private long getContributedMediaSize(@NonNull String pkg, @NonNull UserHandle user) {
try {
return MediaStore.getContributedMediaSize(getContext(), pkg, user);
} catch (IOException e) {
return 0;
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final PackageManager pm = getActivity().getPackageManager();
@@ -76,14 +106,44 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
dialogBuilder.setTitle(appLabel);
dialogBuilder.setPositiveButton(android.R.string.ok, this);
dialogBuilder.setNegativeButton(android.R.string.cancel, this);
dialogBuilder.setMessage(messageBuilder.toString());
String pkg = dialogInfo.appInfo.packageName;
long contributedFileSize = 0;
if (dialogInfo.allUsers) {
List<UserInfo> users = userManager.getUsers();
int numUsers = users.size();
for (int i = 0; i < numUsers; i++) {
UserHandle user = UserHandle.of(users.get(i).id);
contributedFileSize += getContributedMediaSize(pkg, user);
}
} else {
contributedFileSize = getContributedMediaSize(pkg, dialogInfo.user);
}
if (contributedFileSize == 0) {
dialogBuilder.setMessage(messageBuilder.toString());
} else {
LayoutInflater inflater = getContext().getSystemService(LayoutInflater.class);
ViewGroup content = (ViewGroup) inflater.inflate(R.layout.uninstall_content_view, null);
((TextView) content.requireViewById(R.id.message)).setText(messageBuilder.toString());
mClearContributedFiles = content.requireViewById(R.id.checkbox);
mClearContributedFiles.setText(getString(R.string.uninstall_remove_contributed_files,
formatFileSize(getContext(), contributedFileSize)));
dialogBuilder.setView(content);
}
return dialogBuilder.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == Dialog.BUTTON_POSITIVE) {
((UninstallerActivity) getActivity()).startUninstallProgress();
((UninstallerActivity) getActivity()).startUninstallProgress(
mClearContributedFiles != null && mClearContributedFiles.isChecked());
} else {
((UninstallerActivity) getActivity()).dispatchAborted();
}

View File

@@ -99,7 +99,7 @@ public class UninstallAlertFragment extends GuidedStepFragment {
public void onGuidedActionClicked(GuidedAction action) {
if (isAdded()) {
if (action.getId() == GuidedAction.ACTION_ID_OK) {
((UninstallerActivity) getActivity()).startUninstallProgress();
((UninstallerActivity) getActivity()).startUninstallProgress(false);
getActivity().finish();
} else {
((UninstallerActivity) getActivity()).dispatchAborted();