1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.packageinstaller;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.DialogFragment;
23 import android.app.Fragment;
24 import android.app.FragmentTransaction;
25 import android.app.PendingIntent;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageInstaller;
29 import android.content.pm.PackageManager;
30 import android.content.pm.VersionedPackage;
31 import android.os.Bundle;
32 import android.os.Process;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.util.Log;
36 import android.widget.Toast;
37 import androidx.annotation.Nullable;
38 import com.android.packageinstaller.common.EventResultPersister;
39 import com.android.packageinstaller.common.UninstallEventReceiver;
40 
41 /**
42  * Start an uninstallation, show a dialog while uninstalling and return result to the caller.
43  */
44 public class UninstallUninstalling extends Activity implements
45         EventResultPersister.EventResultObserver {
46     private static final String LOG_TAG = UninstallUninstalling.class.getSimpleName();
47 
48     private static final String UNINSTALL_ID = "com.android.packageinstaller.UNINSTALL_ID";
49     private static final String BROADCAST_ACTION =
50             "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
51 
52     static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
53     static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA";
54     public static final String EXTRA_IS_CLONE_USER = "isCloneUser";
55 
56     private int mUninstallId;
57     private ApplicationInfo mAppInfo;
58     private PackageManager.UninstallCompleteCallback mCallback;
59     private boolean mReturnResult;
60     private String mLabel;
61 
62     @Override
onCreate(@ullable Bundle savedInstanceState)63     protected void onCreate(@Nullable Bundle savedInstanceState) {
64         super.onCreate(savedInstanceState);
65 
66         setFinishOnTouchOutside(false);
67 
68         mAppInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
69         mCallback = getIntent().getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
70                 PackageManager.UninstallCompleteCallback.class);
71         mReturnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
72         mLabel = getIntent().getStringExtra(EXTRA_APP_LABEL);
73 
74         try {
75             if (savedInstanceState == null) {
76                 boolean allUsers = getIntent().getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
77                         false);
78                 boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false);
79                 UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
80 
81                 boolean isCloneUser = false;
82                 if (user == null) {
83                     user = Process.myUserHandle();
84                 }
85 
86                 UserManager customUserManager = UninstallUninstalling.this
87                         .createContextAsUser(user, 0)
88                         .getSystemService(UserManager.class);
89                 if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
90                     isCloneUser = true;
91                 }
92 
93                 // Show dialog, which is the whole UI
94                 FragmentTransaction transaction = getFragmentManager().beginTransaction();
95                 Fragment prev = getFragmentManager().findFragmentByTag("dialog");
96                 if (prev != null) {
97                     transaction.remove(prev);
98                 }
99                 DialogFragment dialog = new UninstallUninstallingFragment();
100                 Bundle args = new Bundle();
101                 args.putBoolean(EXTRA_IS_CLONE_USER, isCloneUser);
102                 dialog.setArguments(args);
103                 dialog.setCancelable(false);
104                 dialog.show(transaction, "dialog");
105 
106                 mUninstallId = UninstallEventReceiver.addObserver(this,
107                         EventResultPersister.GENERATE_NEW_ID, this);
108 
109                 Intent broadcastIntent = new Intent(BROADCAST_ACTION);
110                 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
111                 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);
112                 broadcastIntent.setPackage(getPackageName());
113 
114                 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId,
115                         broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
116                                 | PendingIntent.FLAG_MUTABLE);
117 
118                 int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
119                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
120                 flags |= getIntent().getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0);
121 
122                 createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall(
123                         new VersionedPackage(mAppInfo.packageName,
124                                 PackageManager.VERSION_CODE_HIGHEST),
125                         flags, pendingIntent.getIntentSender());
126             } else {
127                 mUninstallId = savedInstanceState.getInt(UNINSTALL_ID);
128                 UninstallEventReceiver.addObserver(this, mUninstallId, this);
129             }
130         } catch (EventResultPersister.OutOfIdsException | IllegalArgumentException e) {
131             Log.e(LOG_TAG, "Fails to start uninstall", e);
132             onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
133                     null, 0);
134         }
135     }
136 
137     @Override
onSaveInstanceState(Bundle outState)138     protected void onSaveInstanceState(Bundle outState) {
139         super.onSaveInstanceState(outState);
140 
141         outState.putInt(UNINSTALL_ID, mUninstallId);
142     }
143 
144     @Override
onBackPressed()145     public void onBackPressed() {
146         // do nothing
147     }
148 
149     @Override
onResult(int status, int legacyStatus, @Nullable String message, int serviceId)150     public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
151         if (mCallback != null) {
152             // The caller will be informed about the result via a callback
153             mCallback.onUninstallComplete(mAppInfo.packageName, legacyStatus, message);
154         } else if (mReturnResult) {
155             // The caller will be informed about the result and might decide to display it
156             Intent result = new Intent();
157 
158             result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
159             setResult(status == PackageInstaller.STATUS_SUCCESS ? Activity.RESULT_OK
160                     : Activity.RESULT_FIRST_USER, result);
161         } else {
162             // This is the rare case that the caller did not ask for the result, but wanted to be
163             // notified via onActivityResult when the installation finishes
164             if (status != PackageInstaller.STATUS_SUCCESS) {
165                 Toast.makeText(this, getString(R.string.uninstall_failed_app, mLabel),
166                         Toast.LENGTH_LONG).show();
167             }
168         }
169         finish();
170     }
171 
172     @Override
onDestroy()173     protected void onDestroy() {
174         UninstallEventReceiver.removeObserver(this, mUninstallId);
175 
176         super.onDestroy();
177     }
178 
179     /**
180      * Dialog that shows that the app is uninstalling.
181      */
182     public static class UninstallUninstallingFragment extends DialogFragment {
183         @Override
onCreateDialog(Bundle savedInstanceState)184         public Dialog onCreateDialog(Bundle savedInstanceState) {
185             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
186 
187             Bundle bundle = getArguments();
188             boolean isCloneUser = false;
189             if (bundle != null) {
190                 isCloneUser = bundle.getBoolean(EXTRA_IS_CLONE_USER);
191             }
192 
193             dialogBuilder.setCancelable(false);
194             if (isCloneUser) {
195                 dialogBuilder.setTitle(getActivity().getString(R.string.uninstalling_cloned_app,
196                         ((UninstallUninstalling) getActivity()).mLabel));
197             } else {
198                 dialogBuilder.setTitle(getActivity().getString(R.string.uninstalling_app,
199                         ((UninstallUninstalling) getActivity()).mLabel));
200             }
201 
202             Dialog dialog = dialogBuilder.create();
203             dialog.setCanceledOnTouchOutside(false);
204 
205             return dialog;
206         }
207     }
208 }
209