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.res.Resources.ID_NULL;
20 
21 import android.Manifest;
22 import android.app.AlertDialog;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.pm.SuspendDialogInfo;
28 import android.content.res.Resources;
29 import android.graphics.drawable.Drawable;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.util.Slog;
33 import android.view.WindowManager;
34 
35 import com.android.internal.R;
36 
37 public class SuspendedAppActivity extends AlertActivity
38         implements DialogInterface.OnClickListener {
39     private static final String TAG = SuspendedAppActivity.class.getSimpleName();
40     private static final String PACKAGE_NAME = "com.android.internal.app";
41 
42     public static final String EXTRA_SUSPENDED_PACKAGE = PACKAGE_NAME + ".extra.SUSPENDED_PACKAGE";
43     public static final String EXTRA_SUSPENDING_PACKAGE =
44             PACKAGE_NAME + ".extra.SUSPENDING_PACKAGE";
45     public static final String EXTRA_DIALOG_INFO = PACKAGE_NAME + ".extra.DIALOG_INFO";
46 
47     private Intent mMoreDetailsIntent;
48     private int mUserId;
49     private PackageManager mPm;
50     private Resources mSuspendingAppResources;
51     private SuspendDialogInfo mSuppliedDialogInfo;
52 
getAppLabel(String packageName)53     private CharSequence getAppLabel(String packageName) {
54         try {
55             return mPm.getApplicationInfoAsUser(packageName, 0, mUserId).loadLabel(mPm);
56         } catch (PackageManager.NameNotFoundException ne) {
57             Slog.e(TAG, "Package " + packageName + " not found", ne);
58         }
59         return packageName;
60     }
61 
getMoreDetailsActivity(String suspendingPackage, String suspendedPackage, int userId)62     private Intent getMoreDetailsActivity(String suspendingPackage, String suspendedPackage,
63             int userId) {
64         final Intent moreDetailsIntent = new Intent(Intent.ACTION_SHOW_SUSPENDED_APP_DETAILS)
65                 .setPackage(suspendingPackage);
66         final String requiredPermission = Manifest.permission.SEND_SHOW_SUSPENDED_APP_DETAILS;
67         final ResolveInfo resolvedInfo = mPm.resolveActivityAsUser(moreDetailsIntent, 0, userId);
68         if (resolvedInfo != null && resolvedInfo.activityInfo != null
69                 && requiredPermission.equals(resolvedInfo.activityInfo.permission)) {
70             moreDetailsIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, suspendedPackage)
71                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
72             return moreDetailsIntent;
73         }
74         return null;
75     }
76 
resolveIcon()77     private Drawable resolveIcon() {
78         final int iconId = (mSuppliedDialogInfo != null) ? mSuppliedDialogInfo.getIconResId()
79                 : ID_NULL;
80         if (iconId != ID_NULL && mSuspendingAppResources != null) {
81             try {
82                 return mSuspendingAppResources.getDrawable(iconId, getTheme());
83             } catch (Resources.NotFoundException nfe) {
84                 Slog.e(TAG, "Could not resolve drawable resource id " + iconId);
85             }
86         }
87         return null;
88     }
89 
resolveTitle()90     private String resolveTitle() {
91         final int titleId = (mSuppliedDialogInfo != null) ? mSuppliedDialogInfo.getTitleResId()
92                 : ID_NULL;
93         if (titleId != ID_NULL && mSuspendingAppResources != null) {
94             try {
95                 return mSuspendingAppResources.getString(titleId);
96             } catch (Resources.NotFoundException nfe) {
97                 Slog.e(TAG, "Could not resolve string resource id " + titleId);
98             }
99         }
100         return getString(R.string.app_suspended_title);
101     }
102 
resolveDialogMessage(String suspendingPkg, String suspendedPkg)103     private String resolveDialogMessage(String suspendingPkg, String suspendedPkg) {
104         final CharSequence suspendedAppLabel = getAppLabel(suspendedPkg);
105         if (mSuppliedDialogInfo != null) {
106             final int messageId = mSuppliedDialogInfo.getDialogMessageResId();
107             final String message = mSuppliedDialogInfo.getDialogMessage();
108             if (messageId != ID_NULL && mSuspendingAppResources != null) {
109                 try {
110                     return mSuspendingAppResources.getString(messageId, suspendedAppLabel);
111                 } catch (Resources.NotFoundException nfe) {
112                     Slog.e(TAG, "Could not resolve string resource id " + messageId);
113                 }
114             } else if (message != null) {
115                 return String.format(getResources().getConfiguration().getLocales().get(0), message,
116                         suspendedAppLabel);
117             }
118         }
119         return getString(R.string.app_suspended_default_message, suspendedAppLabel,
120                 getAppLabel(suspendingPkg));
121     }
122 
resolveNeutralButtonText()123     private String resolveNeutralButtonText() {
124         final int buttonTextId = (mSuppliedDialogInfo != null)
125                 ? mSuppliedDialogInfo.getNeutralButtonTextResId() : ID_NULL;
126         if (buttonTextId != ID_NULL && mSuspendingAppResources != null) {
127             try {
128                 return mSuspendingAppResources.getString(buttonTextId);
129             } catch (Resources.NotFoundException nfe) {
130                 Slog.e(TAG, "Could not resolve string resource id " + buttonTextId);
131             }
132         }
133         return getString(R.string.app_suspended_more_details);
134     }
135 
136     @Override
onCreate(Bundle icicle)137     public void onCreate(Bundle icicle) {
138         super.onCreate(icicle);
139         mPm = getPackageManager();
140         getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
141 
142         final Intent intent = getIntent();
143         mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
144         if (mUserId < 0) {
145             Slog.wtf(TAG, "Invalid user: " + mUserId);
146             finish();
147             return;
148         }
149         final String suspendedPackage = intent.getStringExtra(EXTRA_SUSPENDED_PACKAGE);
150         final String suspendingPackage = intent.getStringExtra(EXTRA_SUSPENDING_PACKAGE);
151         mSuppliedDialogInfo = intent.getParcelableExtra(EXTRA_DIALOG_INFO);
152         if (mSuppliedDialogInfo != null) {
153             try {
154                 mSuspendingAppResources = mPm.getResourcesForApplicationAsUser(suspendingPackage,
155                         mUserId);
156             } catch (PackageManager.NameNotFoundException ne) {
157                 Slog.e(TAG, "Could not find resources for " + suspendingPackage, ne);
158             }
159         }
160 
161         final AlertController.AlertParams ap = mAlertParams;
162         ap.mIcon = resolveIcon();
163         ap.mTitle = resolveTitle();
164         ap.mMessage = resolveDialogMessage(suspendingPackage, suspendedPackage);
165         ap.mPositiveButtonText = getString(android.R.string.ok);
166         mMoreDetailsIntent = getMoreDetailsActivity(suspendingPackage, suspendedPackage, mUserId);
167         if (mMoreDetailsIntent != null) {
168             ap.mNeutralButtonText = resolveNeutralButtonText();
169         }
170         ap.mPositiveButtonListener = ap.mNeutralButtonListener = this;
171         setupAlert();
172     }
173 
174     @Override
onClick(DialogInterface dialog, int which)175     public void onClick(DialogInterface dialog, int which) {
176         switch (which) {
177             case AlertDialog.BUTTON_NEUTRAL:
178                 startActivityAsUser(mMoreDetailsIntent, UserHandle.of(mUserId));
179                 Slog.i(TAG, "Started more details activity");
180                 break;
181         }
182         finish();
183     }
184 
createSuspendedAppInterceptIntent(String suspendedPackage, String suspendingPackage, SuspendDialogInfo dialogInfo, int userId)185     public static Intent createSuspendedAppInterceptIntent(String suspendedPackage,
186             String suspendingPackage, SuspendDialogInfo dialogInfo, int userId) {
187         return new Intent()
188                 .setClassName("android", SuspendedAppActivity.class.getName())
189                 .putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage)
190                 .putExtra(EXTRA_DIALOG_INFO, dialogInfo)
191                 .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage)
192                 .putExtra(Intent.EXTRA_USER_ID, userId)
193                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
194                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
195     }
196 }
197