• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.internal.app;
18 
19 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
20 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
21 import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS;
22 import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
23 import static android.content.res.Resources.ID_NULL;
24 
25 import android.Manifest;
26 import android.annotation.Nullable;
27 import android.app.AlertDialog;
28 import android.app.AppGlobals;
29 import android.app.KeyguardManager;
30 import android.app.usage.UsageStatsManager;
31 import android.content.BroadcastReceiver;
32 import android.content.Context;
33 import android.content.DialogInterface;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.IntentSender;
37 import android.content.pm.IPackageManager;
38 import android.content.pm.PackageManager;
39 import android.content.pm.ResolveInfo;
40 import android.content.pm.SuspendDialogInfo;
41 import android.content.res.Resources;
42 import android.graphics.drawable.Drawable;
43 import android.os.Bundle;
44 import android.os.RemoteException;
45 import android.os.UserHandle;
46 import android.util.Slog;
47 import android.view.WindowManager;
48 
49 import com.android.internal.R;
50 import com.android.internal.util.ArrayUtils;
51 
52 public class SuspendedAppActivity extends AlertActivity
53         implements DialogInterface.OnClickListener {
54     private static final String TAG = SuspendedAppActivity.class.getSimpleName();
55     private static final String PACKAGE_NAME = "com.android.internal.app";
56 
57     public static final String EXTRA_SUSPENDED_PACKAGE = PACKAGE_NAME + ".extra.SUSPENDED_PACKAGE";
58     public static final String EXTRA_SUSPENDING_PACKAGE =
59             PACKAGE_NAME + ".extra.SUSPENDING_PACKAGE";
60     public static final String EXTRA_DIALOG_INFO = PACKAGE_NAME + ".extra.DIALOG_INFO";
61     public static final String EXTRA_ACTIVITY_OPTIONS = PACKAGE_NAME + ".extra.ACTIVITY_OPTIONS";
62     public static final String EXTRA_UNSUSPEND_INTENT = PACKAGE_NAME + ".extra.UNSUSPEND_INTENT";
63 
64     private Intent mMoreDetailsIntent;
65     private IntentSender mOnUnsuspend;
66     private String mSuspendedPackage;
67     private String mSuspendingPackage;
68     private int mNeutralButtonAction;
69     private int mUserId;
70     private PackageManager mPm;
71     private UsageStatsManager mUsm;
72     private Resources mSuspendingAppResources;
73     private SuspendDialogInfo mSuppliedDialogInfo;
74     private Bundle mOptions;
75     private BroadcastReceiver mUnsuspendReceiver = new BroadcastReceiver() {
76         @Override
77         public void onReceive(Context context, Intent intent) {
78             if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(intent.getAction())) {
79                 final String[] unsuspended = intent.getStringArrayExtra(
80                         Intent.EXTRA_CHANGED_PACKAGE_LIST);
81                 if (ArrayUtils.contains(unsuspended, mSuspendedPackage)) {
82                     if (!isFinishing()) {
83                         Slog.w(TAG, "Package " + mSuspendedPackage
84                                 + " got unsuspended while the dialog was visible. Finishing.");
85                         SuspendedAppActivity.this.finish();
86                     }
87                 }
88             }
89         }
90     };
91 
getAppLabel(String packageName)92     private CharSequence getAppLabel(String packageName) {
93         try {
94             return mPm.getApplicationInfoAsUser(packageName, 0, mUserId).loadLabel(mPm);
95         } catch (PackageManager.NameNotFoundException ne) {
96             Slog.e(TAG, "Package " + packageName + " not found", ne);
97         }
98         return packageName;
99     }
100 
getMoreDetailsActivity()101     private Intent getMoreDetailsActivity() {
102         final Intent moreDetailsIntent = new Intent(Intent.ACTION_SHOW_SUSPENDED_APP_DETAILS)
103                 .setPackage(mSuspendingPackage);
104         final String requiredPermission = Manifest.permission.SEND_SHOW_SUSPENDED_APP_DETAILS;
105         final ResolveInfo resolvedInfo = mPm.resolveActivityAsUser(moreDetailsIntent,
106                 MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE, mUserId);
107         if (resolvedInfo != null && resolvedInfo.activityInfo != null
108                 && requiredPermission.equals(resolvedInfo.activityInfo.permission)) {
109             moreDetailsIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, mSuspendedPackage)
110                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
111             return moreDetailsIntent;
112         }
113         return null;
114     }
115 
resolveIcon()116     private Drawable resolveIcon() {
117         final int iconId = (mSuppliedDialogInfo != null) ? mSuppliedDialogInfo.getIconResId()
118                 : ID_NULL;
119         if (iconId != ID_NULL && mSuspendingAppResources != null) {
120             try {
121                 return mSuspendingAppResources.getDrawable(iconId, getTheme());
122             } catch (Resources.NotFoundException nfe) {
123                 Slog.e(TAG, "Could not resolve drawable resource id " + iconId);
124             }
125         }
126         return null;
127     }
128 
resolveTitle()129     private String resolveTitle() {
130         if (mSuppliedDialogInfo != null) {
131             final int titleId = mSuppliedDialogInfo.getTitleResId();
132             final String title = mSuppliedDialogInfo.getTitle();
133             if (titleId != ID_NULL && mSuspendingAppResources != null) {
134                 try {
135                     return mSuspendingAppResources.getString(titleId);
136                 } catch (Resources.NotFoundException nfe) {
137                     Slog.e(TAG, "Could not resolve string resource id " + titleId);
138                 }
139             } else if (title != null) {
140                 return title;
141             }
142         }
143         return getString(R.string.app_suspended_title);
144     }
145 
resolveDialogMessage()146     private String resolveDialogMessage() {
147         final CharSequence suspendedAppLabel = getAppLabel(mSuspendedPackage);
148         if (mSuppliedDialogInfo != null) {
149             final int messageId = mSuppliedDialogInfo.getDialogMessageResId();
150             final String message = mSuppliedDialogInfo.getDialogMessage();
151             if (messageId != ID_NULL && mSuspendingAppResources != null) {
152                 try {
153                     return mSuspendingAppResources.getString(messageId, suspendedAppLabel);
154                 } catch (Resources.NotFoundException nfe) {
155                     Slog.e(TAG, "Could not resolve string resource id " + messageId);
156                 }
157             } else if (message != null) {
158                 return String.format(getResources().getConfiguration().getLocales().get(0), message,
159                         suspendedAppLabel);
160             }
161         }
162         return getString(R.string.app_suspended_default_message, suspendedAppLabel,
163                 getAppLabel(mSuspendingPackage));
164     }
165 
166     /**
167      * Returns a text to be displayed on the neutral button or {@code null} if the button should
168      * not be shown.
169      */
170     @Nullable
resolveNeutralButtonText()171     private String resolveNeutralButtonText() {
172         final int defaultButtonTextId;
173         switch (mNeutralButtonAction) {
174             case BUTTON_ACTION_MORE_DETAILS:
175                 if (mMoreDetailsIntent == null) {
176                     return null;
177                 }
178                 defaultButtonTextId = R.string.app_suspended_more_details;
179                 break;
180             case BUTTON_ACTION_UNSUSPEND:
181                 defaultButtonTextId = R.string.app_suspended_unsuspend_message;
182                 break;
183             default:
184                 Slog.w(TAG, "Unknown neutral button action: " + mNeutralButtonAction);
185                 return null;
186         }
187         if (mSuppliedDialogInfo != null) {
188             final int buttonTextId = mSuppliedDialogInfo.getNeutralButtonTextResId();
189             final String buttonText = mSuppliedDialogInfo.getNeutralButtonText();
190             if (buttonTextId != ID_NULL && mSuspendingAppResources != null) {
191                 try {
192                     return mSuspendingAppResources.getString(buttonTextId);
193                 } catch (Resources.NotFoundException nfe) {
194                     Slog.e(TAG, "Could not resolve string resource id " + buttonTextId);
195                 }
196             } else if (buttonText != null) {
197                 return buttonText;
198             }
199         }
200         return getString(defaultButtonTextId);
201     }
202 
203     @Override
onCreate(Bundle icicle)204     public void onCreate(Bundle icicle) {
205         super.onCreate(icicle);
206         mPm = getPackageManager();
207         mUsm = getSystemService(UsageStatsManager.class);
208         getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
209 
210         final Intent intent = getIntent();
211         mOptions = intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS);
212         mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
213         if (mUserId < 0) {
214             Slog.wtf(TAG, "Invalid user: " + mUserId);
215             finish();
216             return;
217         }
218         mSuspendedPackage = intent.getStringExtra(EXTRA_SUSPENDED_PACKAGE);
219         mSuspendingPackage = intent.getStringExtra(EXTRA_SUSPENDING_PACKAGE);
220         mSuppliedDialogInfo = intent.getParcelableExtra(EXTRA_DIALOG_INFO);
221         mOnUnsuspend = intent.getParcelableExtra(EXTRA_UNSUSPEND_INTENT);
222         if (mSuppliedDialogInfo != null) {
223             try {
224                 mSuspendingAppResources = createContextAsUser(
225                         UserHandle.of(mUserId), /* flags */ 0).getPackageManager()
226                         .getResourcesForApplication(mSuspendingPackage);
227             } catch (PackageManager.NameNotFoundException ne) {
228                 Slog.e(TAG, "Could not find resources for " + mSuspendingPackage, ne);
229             }
230         }
231         mNeutralButtonAction = (mSuppliedDialogInfo != null)
232                 ? mSuppliedDialogInfo.getNeutralButtonAction() : BUTTON_ACTION_MORE_DETAILS;
233         mMoreDetailsIntent = (mNeutralButtonAction == BUTTON_ACTION_MORE_DETAILS)
234                 ? getMoreDetailsActivity() : null;
235 
236         final AlertController.AlertParams ap = mAlertParams;
237         ap.mIcon = resolveIcon();
238         ap.mTitle = resolveTitle();
239         ap.mMessage = resolveDialogMessage();
240         ap.mPositiveButtonText = getString(android.R.string.ok);
241         ap.mNeutralButtonText = resolveNeutralButtonText();
242         ap.mPositiveButtonListener = ap.mNeutralButtonListener = this;
243 
244         requestDismissKeyguardIfNeeded(ap.mMessage);
245 
246         setupAlert();
247 
248         final IntentFilter unsuspendFilter = new IntentFilter(Intent.ACTION_PACKAGES_UNSUSPENDED);
249         registerReceiverAsUser(mUnsuspendReceiver, UserHandle.of(mUserId), unsuspendFilter, null,
250                 null);
251     }
252 
253     @Override
onDestroy()254     protected void onDestroy() {
255         super.onDestroy();
256         unregisterReceiver(mUnsuspendReceiver);
257     }
258 
requestDismissKeyguardIfNeeded(CharSequence dismissMessage)259     private void requestDismissKeyguardIfNeeded(CharSequence dismissMessage) {
260         final KeyguardManager km = getSystemService(KeyguardManager.class);
261         if (km.isKeyguardLocked()) {
262             km.requestDismissKeyguard(this, dismissMessage,
263                     new KeyguardManager.KeyguardDismissCallback() {
264                         @Override
265                         public void onDismissError() {
266                             Slog.e(TAG, "Error while dismissing keyguard."
267                                     + " Keeping the dialog visible.");
268                         }
269 
270                         @Override
271                         public void onDismissCancelled() {
272                             Slog.w(TAG, "Keyguard dismiss was cancelled. Finishing.");
273                             SuspendedAppActivity.this.finish();
274                         }
275                     });
276         }
277     }
278 
279     @Override
onClick(DialogInterface dialog, int which)280     public void onClick(DialogInterface dialog, int which) {
281         switch (which) {
282             case AlertDialog.BUTTON_NEUTRAL:
283                 switch (mNeutralButtonAction) {
284                     case BUTTON_ACTION_MORE_DETAILS:
285                         if (mMoreDetailsIntent != null) {
286                             startActivityAsUser(mMoreDetailsIntent, mOptions,
287                                     UserHandle.of(mUserId));
288                         } else {
289                             Slog.wtf(TAG, "Neutral button should not have existed!");
290                         }
291                         break;
292                     case BUTTON_ACTION_UNSUSPEND:
293                         final IPackageManager ipm = AppGlobals.getPackageManager();
294                         try {
295                             final String[] errored = ipm.setPackagesSuspendedAsUser(
296                                     new String[]{mSuspendedPackage}, false, null, null, null,
297                                     mSuspendingPackage, mUserId);
298                             if (ArrayUtils.contains(errored, mSuspendedPackage)) {
299                                 Slog.e(TAG, "Could not unsuspend " + mSuspendedPackage);
300                                 break;
301                             }
302                         } catch (RemoteException re) {
303                             Slog.e(TAG, "Can't talk to system process", re);
304                             break;
305                         }
306                         final Intent reportUnsuspend = new Intent()
307                                 .setAction(Intent.ACTION_PACKAGE_UNSUSPENDED_MANUALLY)
308                                 .putExtra(Intent.EXTRA_PACKAGE_NAME, mSuspendedPackage)
309                                 .setPackage(mSuspendingPackage)
310                                 .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
311                         sendBroadcastAsUser(reportUnsuspend, UserHandle.of(mUserId));
312 
313                         if (mOnUnsuspend != null) {
314                             try {
315                                 mOnUnsuspend.sendIntent(this, 0, null, null, null);
316                             } catch (IntentSender.SendIntentException e) {
317                                 Slog.e(TAG, "Error while starting intent " + mOnUnsuspend, e);
318                             }
319                         }
320                         break;
321                     default:
322                         Slog.e(TAG, "Unexpected action on neutral button: " + mNeutralButtonAction);
323                         break;
324                 }
325                 break;
326         }
327         mUsm.reportUserInteraction(mSuspendingPackage, mUserId);
328         finish();
329     }
330 
createSuspendedAppInterceptIntent(String suspendedPackage, String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options, IntentSender onUnsuspend, int userId)331     public static Intent createSuspendedAppInterceptIntent(String suspendedPackage,
332             String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options,
333             IntentSender onUnsuspend, int userId) {
334         return new Intent()
335                 .setClassName("android", SuspendedAppActivity.class.getName())
336                 .putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage)
337                 .putExtra(EXTRA_DIALOG_INFO, dialogInfo)
338                 .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage)
339                 .putExtra(EXTRA_UNSUSPEND_INTENT, onUnsuspend)
340                 .putExtra(EXTRA_ACTIVITY_OPTIONS, options)
341                 .putExtra(Intent.EXTRA_USER_ID, userId)
342                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
343                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
344     }
345 }
346