• 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.server.wm;
18 
19 import static android.os.UserHandle.USER_NULL;
20 import static android.os.UserHandle.USER_SYSTEM;
21 import static android.os.UserManager.isHeadlessSystemUserMode;
22 import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
23 
24 import android.annotation.NonNull;
25 import android.annotation.UiThread;
26 import android.annotation.UserIdInt;
27 import android.annotation.WorkerThread;
28 import android.app.AlertDialog;
29 import android.content.BroadcastReceiver;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.Flags;
36 import android.content.pm.UserInfo;
37 import android.content.res.Configuration;
38 import android.os.Build;
39 import android.os.Handler;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.os.SystemProperties;
43 import android.os.UserHandle;
44 import android.system.Os;
45 import android.system.OsConstants;
46 import android.util.ArrayMap;
47 import android.util.ArraySet;
48 import android.util.AtomicFile;
49 import android.util.DisplayMetrics;
50 import android.util.Pair;
51 import android.util.Slog;
52 import android.util.SparseArray;
53 import android.util.Xml;
54 import android.view.ContextThemeWrapper;
55 
56 import com.android.internal.annotations.GuardedBy;
57 import com.android.internal.util.ArrayUtils;
58 import com.android.modules.utils.TypedXmlPullParser;
59 import com.android.modules.utils.TypedXmlSerializer;
60 import com.android.server.IoThread;
61 import com.android.server.LocalServices;
62 import com.android.server.pm.UserManagerInternal;
63 
64 import org.xmlpull.v1.XmlPullParser;
65 import org.xmlpull.v1.XmlPullParserException;
66 
67 import java.io.File;
68 import java.io.FileInputStream;
69 import java.io.FileOutputStream;
70 import java.util.concurrent.atomic.AtomicReference;
71 
72 /**
73  * Manages warning dialogs shown during application lifecycle.
74  */
75 class AppWarnings {
76     private static final String TAG = "AppWarnings";
77     private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
78 
79     public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
80     public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
81     public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
82     public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
83     public static final int FLAG_HIDE_PAGE_SIZE_MISMATCH = 0x10;
84 
85     /**
86      * Map of package flags for each user.
87      * Key: {@literal Pair<userId, packageName>}
88      * Value: Flags
89      */
90     @GuardedBy("mPackageFlags")
91     private final ArrayMap<Pair<Integer, String>, Integer> mPackageFlags = new ArrayMap<>();
92 
93     private final ActivityTaskManagerService mAtm;
94     private final WriteConfigTask mWriteConfigTask;
95     private final UiHandler mUiHandler;
96     private final AtomicFile mConfigFile;
97 
98     private UserManagerInternal mUserManagerInternal;
99 
100     /**
101      * Maps of app warning dialogs for each user.
102      * Key: userId
103      * Value: The warning dialog for specific user
104      */
105     private SparseArray<UnsupportedDisplaySizeDialog> mUnsupportedDisplaySizeDialogs;
106     private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs;
107     private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs;
108     private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs;
109     private SparseArray<PageSizeMismatchDialog> mPageSizeMismatchDialogs;
110 
111     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
112     private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
113             new ArraySet<>();
114 
115     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
alwaysShowUnsupportedCompileSdkWarning(ComponentName activity)116     void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
117         mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity);
118     }
119 
120     /** Creates a new warning dialog manager. */
AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, Handler uiHandler, File systemDir)121     public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
122             Handler uiHandler, File systemDir) {
123         mAtm = atm;
124         mWriteConfigTask = new WriteConfigTask();
125         mUiHandler = new UiHandler(uiHandler.getLooper());
126         mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
127     }
128 
129     /**
130      * Called when ActivityManagerService receives its systemReady call during boot.
131      */
onSystemReady()132     void onSystemReady() {
133         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
134         readConfigFromFileAmsThread();
135 
136         if (!isVisibleBackgroundUsersEnabled()) {
137             return;
138         }
139 
140         mUserManagerInternal.addUserLifecycleListener(
141                 new UserManagerInternal.UserLifecycleListener() {
142                     @Override
143                     public void onUserRemoved(UserInfo user) {
144                         // Ignore profile user.
145                         if (!user.isFull()) {
146                             return;
147                         }
148                         // Dismiss all warnings and clear all package flags for the user.
149                         mUiHandler.hideDialogsForPackage(/* name= */ null, user.id);
150                         clearAllPackageFlagsForUser(user.id);
151                     }
152                 });
153     }
154 
155     /**
156      * Shows the "unsupported display size" warning, if necessary.
157      *
158      * @param r activity record for which the warning may be displayed
159      */
showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r)160     public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
161         final DisplayContent dc = r.getDisplayContent();
162         final Configuration config = dc == null
163                 ? mAtm.getGlobalConfiguration() : dc.getConfiguration();
164         if (config.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
165                 && r.info.applicationInfo.requiresSmallestWidthDp
166                 > config.smallestScreenWidthDp) {
167             mUiHandler.showUnsupportedDisplaySizeDialog(r);
168         }
169     }
170 
171     /**
172      * Shows the "unsupported compile SDK" warning, if necessary.
173      *
174      * @param r activity record for which the warning may be displayed
175      */
showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r)176     public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
177         if (r.info.applicationInfo.compileSdkVersion == 0
178                 || r.info.applicationInfo.compileSdkVersionCodename == null) {
179             // We don't know enough about this package. Abort!
180             return;
181         }
182 
183         // TODO(b/75318890): Need to move this to when the app actually crashes.
184         if (/*ActivityManager.isRunningInTestHarness()
185                 &&*/ !mAlwaysShowUnsupportedCompileSdkWarningActivities.contains(
186                         r.mActivityComponent)) {
187             // Don't show warning if we are running in a test harness and we don't have to always
188             // show for this activity.
189             return;
190         }
191 
192         // If the application was built against an pre-release SDK that's older than the current
193         // platform OR if the current platform is pre-release and older than the SDK against which
194         // the application was built OR both are pre-release with the same SDK_INT but different
195         // codenames (e.g. simultaneous pre-release development), then we're likely to run into
196         // compatibility issues. Warn the user and offer to check for an update.
197         final int compileSdk = r.info.applicationInfo.compileSdkVersion;
198         final int platformSdk = Build.VERSION.SDK_INT;
199         final boolean isCompileSdkPreview =
200                 !"REL".equals(r.info.applicationInfo.compileSdkVersionCodename);
201         final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
202         if ((isCompileSdkPreview && compileSdk < platformSdk)
203                 || (isPlatformSdkPreview && platformSdk < compileSdk)
204                 || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
205                     && !Build.VERSION.CODENAME.equals(
206                             r.info.applicationInfo.compileSdkVersionCodename))) {
207             mUiHandler.showUnsupportedCompileSdkDialog(r);
208         }
209     }
210 
211     /**
212      * Shows the "deprecated target sdk" warning, if necessary.
213      *
214      * @param r activity record for which the warning may be displayed
215      */
showDeprecatedTargetDialogIfNeeded(ActivityRecord r)216     public void showDeprecatedTargetDialogIfNeeded(ActivityRecord r) {
217         // The warning dialog can be disabled for debugging or testing purposes
218         final boolean disableDeprecatedTargetSdkDialog = SystemProperties.getBoolean(
219                 "debug.wm.disable_deprecated_target_sdk_dialog", false);
220         if (r.info.applicationInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT
221                 && !disableDeprecatedTargetSdkDialog) {
222             mUiHandler.showDeprecatedTargetDialog(r);
223         }
224     }
225 
226     /**
227      * Shows the "deprecated abi" warning, if necessary. This can only happen is the device
228      * supports both 64-bit and 32-bit ABIs, and the app only contains 32-bit libraries. The app
229      * cannot be installed if the device only supports 64-bit ABI while the app contains only 32-bit
230      * libraries.
231      *
232      * @param r activity record for which the warning may be displayed
233      */
showDeprecatedAbiDialogIfNeeded(ActivityRecord r)234     public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) {
235         final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt
236                 & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0;
237         if (isUsingAbiOverride) {
238             // The abiOverride flag was specified during installation, which means that if the app
239             // is currently running in 32-bit mode, it is intended. Do not show the warning dialog.
240             return;
241         }
242         // The warning dialog can also be disabled for debugging purpose
243         final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean(
244                 "debug.wm.disable_deprecated_abi_dialog", false);
245         if (disableDeprecatedAbiDialog) {
246             return;
247         }
248         final String appPrimaryAbi = r.info.applicationInfo.primaryCpuAbi;
249         final String appSecondaryAbi = r.info.applicationInfo.secondaryCpuAbi;
250         final boolean appContainsOnly32bitLibraries =
251                 (appPrimaryAbi != null && appSecondaryAbi == null && !appPrimaryAbi.contains("64"));
252         final boolean is64BitDevice =
253                 ArrayUtils.find(Build.SUPPORTED_ABIS, abi -> abi.contains("64")) != null;
254         if (is64BitDevice && appContainsOnly32bitLibraries) {
255             mUiHandler.showDeprecatedAbiDialog(r);
256         }
257     }
258 
showPageSizeMismatchDialogIfNeeded(ActivityRecord r)259     public void showPageSizeMismatchDialogIfNeeded(ActivityRecord r) {
260         // Don't show dialog if the app compat is enabled using property
261         final boolean appCompatEnabled = SystemProperties.getBoolean(
262                 "bionic.linker.16kb.app_compat.enabled", false);
263         if (appCompatEnabled) {
264             return;
265         }
266         boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384;
267         if (is16KbDevice) {
268             mUiHandler.showPageSizeMismatchDialog(r);
269         }
270     }
271 
272     /**
273      * Called when an activity is being started.
274      *
275      * @param r record for the activity being started
276      */
onStartActivity(ActivityRecord r)277     public void onStartActivity(ActivityRecord r) {
278         showUnsupportedCompileSdkDialogIfNeeded(r);
279         showUnsupportedDisplaySizeDialogIfNeeded(r);
280         showDeprecatedTargetDialogIfNeeded(r);
281         showDeprecatedAbiDialogIfNeeded(r);
282         if (Flags.appCompatOption16kb()) {
283             showPageSizeMismatchDialogIfNeeded(r);
284         }
285     }
286 
287     /**
288      * Called when an activity was previously started and is being resumed.
289      *
290      * @param r record for the activity being resumed
291      */
onResumeActivity(ActivityRecord r)292     public void onResumeActivity(ActivityRecord r) {
293         showUnsupportedDisplaySizeDialogIfNeeded(r);
294     }
295 
296     /**
297      * Called by ActivityManagerService when package data has been cleared.
298      *
299      * @param name the package whose data has been cleared
300      * @param userId the user where the package resides.
301      */
onPackageDataCleared(String name, int userId)302     public void onPackageDataCleared(String name, int userId) {
303         removePackageAndHideDialogs(name, userId);
304     }
305 
306     /**
307      * Called by ActivityManagerService when a package has been uninstalled.
308      *
309      * @param name the package that has been uninstalled
310      * @param userId the user where the package resides.
311      */
onPackageUninstalled(String name, int userId)312     public void onPackageUninstalled(String name, int userId) {
313         removePackageAndHideDialogs(name, userId);
314     }
315 
316     /**
317      * Called by ActivityManagerService when the default display density has changed.
318      */
onDensityChanged()319     public void onDensityChanged() {
320         mUiHandler.hideUnsupportedDisplaySizeDialog();
321     }
322 
323     /**
324      * Does what it says on the tin.
325      */
removePackageAndHideDialogs(String name, int userId)326     private void removePackageAndHideDialogs(String name, int userId) {
327         // Per-user AppWarnings only affects the behavior of the devices that enable the visible
328         // background users.
329         // To preserve existing behavior of the other devices, handle AppWarnings as a system user
330         // regardless of the actual user.
331         if (!isVisibleBackgroundUsersEnabled()) {
332             userId = USER_SYSTEM;
333         } else {
334             // If the userId is of a profile, use the parent user ID,
335             // since the warning dialogs and the flags for a package are handled per profile group.
336             userId = mUserManagerInternal.getProfileParentId(userId);
337         }
338 
339         mUiHandler.hideDialogsForPackage(name, userId);
340 
341         synchronized (mPackageFlags) {
342             final Pair<Integer, String> packageKey = Pair.create(userId, name);
343             if (mPackageFlags.remove(packageKey) != null) {
344                 mWriteConfigTask.schedule();
345             }
346         }
347     }
348 
349     /**
350      * Hides the "unsupported display size" warning.
351      * <p>
352      * <strong>Note:</strong> Must be called on the UI thread.
353      */
354     @UiThread
hideUnsupportedDisplaySizeDialogUiThread()355     private void hideUnsupportedDisplaySizeDialogUiThread() {
356         if (mUnsupportedDisplaySizeDialogs == null) {
357             return;
358         }
359 
360         for (int i = 0; i < mUnsupportedDisplaySizeDialogs.size(); i++) {
361             mUnsupportedDisplaySizeDialogs.valueAt(i).dismiss();
362         }
363         mUnsupportedDisplaySizeDialogs.clear();
364     }
365 
366     /**
367      * Shows the "unsupported display size" warning for the given application.
368      * <p>
369      * <strong>Note:</strong> Must be called on the UI thread.
370      *
371      * @param ar record for the activity that triggered the warning
372      */
373     @UiThread
showUnsupportedDisplaySizeDialogUiThread(@onNull ActivityRecord ar)374     private void showUnsupportedDisplaySizeDialogUiThread(@NonNull ActivityRecord ar) {
375         final int userId = getUserIdForActivity(ar);
376         UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog;
377         if (mUnsupportedDisplaySizeDialogs != null) {
378             unsupportedDisplaySizeDialog = mUnsupportedDisplaySizeDialogs.get(userId);
379             if (unsupportedDisplaySizeDialog != null) {
380                 unsupportedDisplaySizeDialog.dismiss();
381                 mUnsupportedDisplaySizeDialogs.remove(userId);
382             }
383         }
384         if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
385             unsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
386                     AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
387             unsupportedDisplaySizeDialog.show();
388             if (mUnsupportedDisplaySizeDialogs == null) {
389                 mUnsupportedDisplaySizeDialogs = new SparseArray<>();
390             }
391             mUnsupportedDisplaySizeDialogs.put(userId, unsupportedDisplaySizeDialog);
392         }
393     }
394 
395     /**
396      * Shows the "unsupported compile SDK" warning for the given application.
397      * <p>
398      * <strong>Note:</strong> Must be called on the UI thread.
399      *
400      * @param ar record for the activity that triggered the warning
401      */
402     @UiThread
showUnsupportedCompileSdkDialogUiThread(@onNull ActivityRecord ar)403     private void showUnsupportedCompileSdkDialogUiThread(@NonNull ActivityRecord ar) {
404         final int userId = getUserIdForActivity(ar);
405         UnsupportedCompileSdkDialog unsupportedCompileSdkDialog;
406         if (mUnsupportedCompileSdkDialogs != null) {
407             unsupportedCompileSdkDialog = mUnsupportedCompileSdkDialogs.get(userId);
408             if (unsupportedCompileSdkDialog != null) {
409                 unsupportedCompileSdkDialog.dismiss();
410                 mUnsupportedCompileSdkDialogs.remove(userId);
411             }
412         }
413         if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
414             unsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
415                     AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
416             unsupportedCompileSdkDialog.show();
417             if (mUnsupportedCompileSdkDialogs == null) {
418                 mUnsupportedCompileSdkDialogs = new SparseArray<>();
419             }
420             mUnsupportedCompileSdkDialogs.put(userId, unsupportedCompileSdkDialog);
421         }
422     }
423 
424     /**
425      * Shows the "deprecated target sdk version" warning for the given application.
426      * <p>
427      * <strong>Note:</strong> Must be called on the UI thread.
428      *
429      * @param ar record for the activity that triggered the warning
430      */
431     @UiThread
showDeprecatedTargetSdkDialogUiThread(@onNull ActivityRecord ar)432     private void showDeprecatedTargetSdkDialogUiThread(@NonNull ActivityRecord ar) {
433         final int userId = getUserIdForActivity(ar);
434         DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog;
435         if (mDeprecatedTargetSdkVersionDialogs != null) {
436             deprecatedTargetSdkVersionDialog = mDeprecatedTargetSdkVersionDialogs.get(userId);
437             if (deprecatedTargetSdkVersionDialog != null) {
438                 deprecatedTargetSdkVersionDialog.dismiss();
439                 mDeprecatedTargetSdkVersionDialogs.remove(userId);
440             }
441         }
442         if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
443             deprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
444                     AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
445             deprecatedTargetSdkVersionDialog.show();
446             if (mDeprecatedTargetSdkVersionDialogs == null) {
447                 mDeprecatedTargetSdkVersionDialogs = new SparseArray<>();
448             }
449             mDeprecatedTargetSdkVersionDialogs.put(userId, deprecatedTargetSdkVersionDialog);
450         }
451     }
452 
453     /**
454      * Shows the "deprecated abi" warning for the given application.
455      * <p>
456      * <strong>Note:</strong> Must be called on the UI thread.
457      *
458      * @param ar record for the activity that triggered the warning
459      */
460     @UiThread
showDeprecatedAbiDialogUiThread(@onNull ActivityRecord ar)461     private void showDeprecatedAbiDialogUiThread(@NonNull ActivityRecord ar) {
462         final int userId = getUserIdForActivity(ar);
463         DeprecatedAbiDialog deprecatedAbiDialog;
464         if (mDeprecatedAbiDialogs != null) {
465             deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId);
466             if (deprecatedAbiDialog != null) {
467                 deprecatedAbiDialog.dismiss();
468                 mDeprecatedAbiDialogs.remove(userId);
469             }
470         }
471         if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) {
472             deprecatedAbiDialog = new DeprecatedAbiDialog(
473                     AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
474             deprecatedAbiDialog.show();
475             if (mDeprecatedAbiDialogs == null) {
476                 mDeprecatedAbiDialogs = new SparseArray<>();
477             }
478             mDeprecatedAbiDialogs.put(userId, deprecatedAbiDialog);
479         }
480     }
481 
482     @UiThread
showPageSizeMismatchDialogUiThread(@onNull ActivityRecord ar)483     private void showPageSizeMismatchDialogUiThread(@NonNull ActivityRecord ar) {
484         String warning =
485                 mAtm.mContext
486                         .getPackageManager()
487                         .getPageSizeCompatWarningMessage(ar.info.packageName);
488         if (warning == null) {
489             return;
490         }
491 
492         final int userId = getUserIdForActivity(ar);
493         PageSizeMismatchDialog pageSizeMismatchDialog;
494         if (mPageSizeMismatchDialogs != null) {
495             pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
496             if (pageSizeMismatchDialog != null) {
497                 pageSizeMismatchDialog.dismiss();
498                 mPageSizeMismatchDialogs.remove(userId);
499             }
500         }
501         if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) {
502             Context context =  getUiContextForActivity(ar);
503             // PageSizeMismatchDialog has link in message which should open in browser.
504             // Starting activity from non-activity context is not allowed and flag
505             // FLAG_ACTIVITY_NEW_TASK is needed to start activity.
506             context =  new ContextThemeWrapper(context, context.getThemeResId()) {
507                 @Override
508                 public void startActivity(Intent intent) {
509                     // PageSizeMismatch dialog stays on top of the browser even after opening link
510                     // set broadcast to close the dialog when link has been clicked.
511                     sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
512 
513                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
514                     super.startActivity(intent);
515                 }
516             };
517             pageSizeMismatchDialog =
518                     new PageSizeMismatchDialog(
519                             AppWarnings.this,
520                             context,
521                             ar.info.applicationInfo,
522                             userId,
523                             warning);
524             pageSizeMismatchDialog.show();
525             if (mPageSizeMismatchDialogs == null) {
526                 mPageSizeMismatchDialogs = new SparseArray<>();
527             }
528             mPageSizeMismatchDialogs.put(userId, pageSizeMismatchDialog);
529         }
530     }
531 
532     /**
533      * Dismisses all warnings for the given package.
534      * <p>
535      * <strong>Note:</strong> Must be called on the UI thread.
536      *
537      * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
538      *             all warnings
539      * @param userId the user where the package resides.
540      */
541     @UiThread
hideDialogsForPackageUiThread(String name, int userId)542     private void hideDialogsForPackageUiThread(String name, int userId) {
543         // Hides the "unsupported display" dialog if necessary.
544         if (mUnsupportedDisplaySizeDialogs != null) {
545             UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog =
546                     mUnsupportedDisplaySizeDialogs.get(userId);
547             if (unsupportedDisplaySizeDialog != null && (name == null || name.equals(
548                     unsupportedDisplaySizeDialog.mPackageName))) {
549                 unsupportedDisplaySizeDialog.dismiss();
550                 mUnsupportedDisplaySizeDialogs.remove(userId);
551             }
552         }
553 
554         // Hides the "unsupported compile SDK" dialog if necessary.
555         if (mUnsupportedCompileSdkDialogs != null) {
556             UnsupportedCompileSdkDialog unsupportedCompileSdkDialog =
557                     mUnsupportedCompileSdkDialogs.get(userId);
558             if (unsupportedCompileSdkDialog != null && (name == null || name.equals(
559                     unsupportedCompileSdkDialog.mPackageName))) {
560                 unsupportedCompileSdkDialog.dismiss();
561                 mUnsupportedCompileSdkDialogs.remove(userId);
562             }
563         }
564 
565         // Hides the "deprecated target sdk version" dialog if necessary.
566         if (mDeprecatedTargetSdkVersionDialogs != null) {
567             DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog =
568                     mDeprecatedTargetSdkVersionDialogs.get(userId);
569             if (deprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
570                     deprecatedTargetSdkVersionDialog.mPackageName))) {
571                 deprecatedTargetSdkVersionDialog.dismiss();
572                 mDeprecatedTargetSdkVersionDialogs.remove(userId);
573             }
574         }
575 
576         // Hides the "deprecated abi" dialog if necessary.
577         if (mDeprecatedAbiDialogs != null) {
578             DeprecatedAbiDialog deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId);
579             if (deprecatedAbiDialog != null && (name == null || name.equals(
580                     deprecatedAbiDialog.mPackageName))) {
581                 deprecatedAbiDialog.dismiss();
582                 mDeprecatedAbiDialogs.remove(userId);
583             }
584         }
585 
586         // Hides the "page size app compat" dialog if necessary.
587         if (mPageSizeMismatchDialogs != null) {
588             PageSizeMismatchDialog pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
589             if (pageSizeMismatchDialog != null
590                     && (name == null || name.equals(pageSizeMismatchDialog.mPackageName))) {
591                 pageSizeMismatchDialog.dismiss();
592                 mPageSizeMismatchDialogs.remove(userId);
593             }
594         }
595     }
596 
597     /**
598      * Returns the value of the flag for the given package.
599      *
600      * @param userId the user where the package resides.
601      * @param name the package from which to retrieve the flag
602      * @param flag the bitmask for the flag to retrieve
603      * @return {@code true} if the flag is enabled, {@code false} otherwise
604      */
hasPackageFlag(int userId, String name, int flag)605     boolean hasPackageFlag(int userId, String name, int flag) {
606         return (getPackageFlags(userId, name) & flag) == flag;
607     }
608 
609     /**
610      * Sets the flag for the given package to the specified value.
611      *
612      * @param userId the user where the package resides.
613      * @param name the package on which to set the flag
614      * @param flag the bitmask for flag to set
615      * @param enabled the value to set for the flag
616      */
setPackageFlag(int userId, String name, int flag, boolean enabled)617     void setPackageFlag(int userId, String name, int flag, boolean enabled) {
618         synchronized (mPackageFlags) {
619             final int curFlags = getPackageFlags(userId, name);
620             final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag);
621             if (curFlags != newFlags) {
622                 final Pair<Integer, String> packageKey = Pair.create(userId, name);
623                 if (newFlags != 0) {
624                     mPackageFlags.put(packageKey, newFlags);
625                 } else {
626                     mPackageFlags.remove(packageKey);
627                 }
628                 mWriteConfigTask.schedule();
629             }
630         }
631     }
632 
633     /**
634      * Returns the bitmask of flags set for the specified package.
635      */
getPackageFlags(int userId, String packageName)636     private int getPackageFlags(int userId, String packageName) {
637         synchronized (mPackageFlags) {
638             final Pair<Integer, String> packageKey = Pair.create(userId, packageName);
639             return mPackageFlags.getOrDefault(packageKey, 0);
640         }
641     }
642 
643     /**
644      * Clear all the package flags for given user.
645      */
clearAllPackageFlagsForUser(int userId)646     private void clearAllPackageFlagsForUser(int userId) {
647         synchronized (mPackageFlags) {
648             boolean hasPackageFlagsForUser = false;
649             for (int i = mPackageFlags.size() - 1; i >= 0; i--) {
650                 Pair<Integer, String> key = mPackageFlags.keyAt(i);
651                 if (key.first == userId) {
652                     hasPackageFlagsForUser = true;
653                     mPackageFlags.remove(key);
654                 }
655             }
656 
657             if (hasPackageFlagsForUser) {
658                 mWriteConfigTask.schedule();
659             }
660         }
661     }
662 
663     /**
664      * Returns the user ID for handling AppWarnings per user.
665      * Per-user AppWarnings only affects the behavior of the devices that enable
666      * the visible background users.
667      * If the device doesn't enable visible background users, it will return the system user ID
668      * for handling AppWarnings as a system user regardless of the actual user
669      * to preserve existing behavior of the device.
670      * Otherwise, it will return the main user (i.e., not a profile) that is assigned to the display
671      * where the activity is launched.
672      */
getUserIdForActivity(@onNull ActivityRecord ar)673     private @UserIdInt int getUserIdForActivity(@NonNull ActivityRecord ar) {
674         if (!isVisibleBackgroundUsersEnabled()) {
675             return USER_SYSTEM;
676         }
677 
678         if (ar.mUserId == USER_SYSTEM) {
679             return getUserAssignedToDisplay(ar.mDisplayContent.getDisplayId());
680         }
681 
682         return mUserManagerInternal.getProfileParentId(ar.mUserId);
683     }
684 
685     /**
686      * Returns the UI context for handling AppWarnings per user.
687      * Per-user AppWarnings only affects the behavior of the devices that enable
688      * the visible background users.
689      * If the device enables the visible background users, it will return the UI context associated
690      * with the assigned user and the display where the activity is launched.
691      * If the HSUM device doesn't enable the visible background users, it will return the UI context
692      * associated with the current user and the default display.
693      * Otherwise, it will return the UI context associated with the system user and the default
694      * display.
695      */
getUiContextForActivity(@onNull ActivityRecord ar)696     private Context getUiContextForActivity(@NonNull ActivityRecord ar) {
697         if (!isVisibleBackgroundUsersEnabled()) {
698             if (!isHeadlessSystemUserMode()) {
699                 return mAtm.getUiContext();
700             }
701 
702             Context uiContextForCurrentUser = mAtm.getUiContext().createContextAsUser(
703                     new UserHandle(mAtm.getCurrentUserId()), /* flags= */ 0);
704             return uiContextForCurrentUser;
705         }
706 
707         DisplayContent dc = ar.mDisplayContent;
708         Context systemUiContext = dc.getDisplayPolicy().getSystemUiContext();
709         int assignedUser = getUserAssignedToDisplay(dc.getDisplayId());
710         Context uiContextForUser = systemUiContext.createContextAsUser(
711                 new UserHandle(assignedUser), /* flags= */ 0);
712         return uiContextForUser;
713     }
714 
715     /**
716      * Returns the main user that is assigned to the display.
717      *
718      * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
719      */
getUserAssignedToDisplay(int displayId)720     private @UserIdInt int getUserAssignedToDisplay(int displayId) {
721         return mUserManagerInternal.getUserAssignedToDisplay(displayId);
722     }
723 
724     /**
725      * Handles messages on the system process UI thread.
726      */
727     private final class UiHandler extends Handler {
728         private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
729         private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
730         private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
731         private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
732         private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
733         private static final int MSG_SHOW_DEPRECATED_ABI_DIALOG = 6;
734         private static final int MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG = 7;
735 
UiHandler(Looper looper)736         public UiHandler(Looper looper) {
737             super(looper, null, true);
738         }
739 
740         @Override
handleMessage(Message msg)741         public void handleMessage(Message msg) {
742             switch (msg.what) {
743                 case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
744                     final ActivityRecord ar = (ActivityRecord) msg.obj;
745                     showUnsupportedDisplaySizeDialogUiThread(ar);
746                 } break;
747                 case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
748                     hideUnsupportedDisplaySizeDialogUiThread();
749                 } break;
750                 case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
751                     final ActivityRecord ar = (ActivityRecord) msg.obj;
752                     showUnsupportedCompileSdkDialogUiThread(ar);
753                 } break;
754                 case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
755                     final String name = (String) msg.obj;
756                     final int userId = (int) msg.arg1;
757                     hideDialogsForPackageUiThread(name, userId);
758                 } break;
759                 case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: {
760                     final ActivityRecord ar = (ActivityRecord) msg.obj;
761                     showDeprecatedTargetSdkDialogUiThread(ar);
762                 } break;
763                 case MSG_SHOW_DEPRECATED_ABI_DIALOG: {
764                     final ActivityRecord ar = (ActivityRecord) msg.obj;
765                     showDeprecatedAbiDialogUiThread(ar);
766                 } break;
767                 case MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG: {
768                     final ActivityRecord ar = (ActivityRecord) msg.obj;
769                     showPageSizeMismatchDialogUiThread(ar);
770                 } break;
771             }
772         }
773 
showUnsupportedDisplaySizeDialog(ActivityRecord r)774         public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
775             removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
776             obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
777         }
778 
hideUnsupportedDisplaySizeDialog()779         public void hideUnsupportedDisplaySizeDialog() {
780             removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
781             sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
782         }
783 
showUnsupportedCompileSdkDialog(ActivityRecord r)784         public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
785             removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
786             obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
787         }
788 
showDeprecatedTargetDialog(ActivityRecord r)789         public void showDeprecatedTargetDialog(ActivityRecord r) {
790             removeMessages(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG);
791             obtainMessage(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG, r).sendToTarget();
792         }
793 
showDeprecatedAbiDialog(ActivityRecord r)794         public void showDeprecatedAbiDialog(ActivityRecord r) {
795             removeMessages(MSG_SHOW_DEPRECATED_ABI_DIALOG);
796             obtainMessage(MSG_SHOW_DEPRECATED_ABI_DIALOG, r).sendToTarget();
797         }
798 
hideDialogsForPackage(String name, int userId)799         public void hideDialogsForPackage(String name, int userId) {
800             obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget();
801         }
802 
showPageSizeMismatchDialog(ActivityRecord r)803         public void showPageSizeMismatchDialog(ActivityRecord r) {
804             removeMessages(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG);
805             obtainMessage(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG, r).sendToTarget();
806         }
807     }
808 
809     static class BaseDialog {
810         final AppWarnings mManager;
811         final Context mUiContext;
812         final String mPackageName;
813         final int mUserId;
814         AlertDialog mDialog;
815         private BroadcastReceiver mCloseReceiver;
816 
BaseDialog(AppWarnings manager, Context uiContext, String packageName, int userId)817         BaseDialog(AppWarnings manager, Context uiContext, String packageName, int userId) {
818             mManager = manager;
819             mUiContext = uiContext;
820             mPackageName = packageName;
821             mUserId = userId;
822         }
823 
824         @UiThread
show()825         void show() {
826             if (mDialog == null) return;
827             if (mCloseReceiver == null) {
828                 mCloseReceiver = new BroadcastReceiver() {
829                     @Override
830                     public void onReceive(Context context, Intent intent) {
831                         if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
832                             mManager.mUiHandler.hideDialogsForPackage(mPackageName, mUserId);
833                         }
834                     }
835                 };
836                 mUiContext.registerReceiver(mCloseReceiver,
837                         new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
838                         Context.RECEIVER_EXPORTED);
839             }
840             Slog.w(TAG, "Showing " + getClass().getSimpleName() + " for package " + mPackageName);
841             mDialog.show();
842         }
843 
844         @UiThread
dismiss()845         void dismiss() {
846             if (mDialog == null) return;
847             if (mCloseReceiver != null) {
848                 mUiContext.unregisterReceiver(mCloseReceiver);
849                 mCloseReceiver = null;
850             }
851             mDialog.dismiss();
852             mDialog = null;
853         }
854     }
855 
856     private final class WriteConfigTask implements Runnable {
857         private static final long WRITE_CONFIG_DELAY_MS = 10000;
858         final AtomicReference<ArrayMap<Pair<Integer, String>, Integer>> mPendingPackageFlags =
859                 new AtomicReference<>();
860 
861         @Override
run()862         public void run() {
863             final ArrayMap<Pair<Integer, String>, Integer> packageFlags =
864                     mPendingPackageFlags.getAndSet(null);
865             if (packageFlags != null) {
866                 writeConfigToFile(packageFlags);
867             }
868         }
869 
870         @GuardedBy("mPackageFlags")
schedule()871         void schedule() {
872             if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) {
873                 IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS);
874             }
875         }
876     }
877 
878     /** Writes the configuration file. */
879     @WorkerThread
writeConfigToFile(@onNull ArrayMap<Pair<Integer, String>, Integer> packageFlags)880     private void writeConfigToFile(@NonNull ArrayMap<Pair<Integer, String>, Integer> packageFlags) {
881         FileOutputStream fos = null;
882         try {
883             fos = mConfigFile.startWrite();
884 
885             final TypedXmlSerializer out = Xml.resolveSerializer(fos);
886             out.startDocument(null, true);
887             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
888             out.startTag(null, "packages");
889 
890             for (int i = 0; i < packageFlags.size(); i++) {
891                 final Pair<Integer, String> key = packageFlags.keyAt(i);
892                 final int userId = key.first;
893                 final String packageName = key.second;
894                 final int mode = packageFlags.valueAt(i);
895                 if (mode == 0) {
896                     continue;
897                 }
898                 out.startTag(null, "package");
899                 out.attributeInt(null, "user", userId);
900                 out.attribute(null, "name", packageName);
901                 out.attributeInt(null, "flags", mode);
902                 out.endTag(null, "package");
903             }
904 
905             out.endTag(null, "packages");
906             out.endDocument();
907 
908             mConfigFile.finishWrite(fos);
909         } catch (java.io.IOException e1) {
910             Slog.w(TAG, "Error writing package metadata", e1);
911             if (fos != null) {
912                 mConfigFile.failWrite(fos);
913             }
914         }
915     }
916 
917     /**
918      * Reads the configuration file and populates the package flags.
919      * <p>
920      * <strong>Note:</strong> Must be called from #onSystemReady() (and thus on the
921      * ActivityManagerService thread) since we don't synchronize on config.
922      */
readConfigFromFileAmsThread()923     private void readConfigFromFileAmsThread() {
924         FileInputStream fis = null;
925 
926         try {
927             fis = mConfigFile.openRead();
928 
929             final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
930 
931             int eventType = parser.getEventType();
932             while (eventType != XmlPullParser.START_TAG &&
933                     eventType != XmlPullParser.END_DOCUMENT) {
934                 eventType = parser.next();
935             }
936             if (eventType == XmlPullParser.END_DOCUMENT) {
937                 return;
938             }
939 
940             String tagName = parser.getName();
941             if ("packages".equals(tagName)) {
942                 eventType = parser.next();
943                 boolean writeConfigToFileNeeded = false;
944                 do {
945                     if (eventType == XmlPullParser.START_TAG) {
946                         tagName = parser.getName();
947                         if (parser.getDepth() == 2) {
948                             if ("package".equals(tagName)) {
949                                 final int userId = parser.getAttributeInt(
950                                         null, "user", USER_NULL);
951                                 final String name = parser.getAttributeValue(null, "name");
952                                 if (name != null) {
953                                     int flagsInt = parser.getAttributeInt(null, "flags", 0);
954                                     if (userId != USER_NULL) {
955                                         final Pair<Integer, String> packageKey =
956                                                 Pair.create(userId, name);
957                                         mPackageFlags.put(packageKey, flagsInt);
958                                     } else {
959                                         // This is for compatibility with existing configuration
960                                         // file written from legacy logic(pre-V) which does not have
961                                         // the flags per-user. (b/296334639)
962                                         writeConfigToFileNeeded = true;
963                                         if (!isVisibleBackgroundUsersEnabled()) {
964                                             // To preserve existing behavior of the devices that
965                                             // doesn't enable visible background users, populate
966                                             // the flags for a package as the system user.
967                                             final Pair<Integer, String> packageKey =
968                                                     Pair.create(USER_SYSTEM, name);
969                                             mPackageFlags.put(packageKey, flagsInt);
970                                         } else {
971                                             // To manage the flags per user in the device that
972                                             // enable visible background users, populate the flags
973                                             // for all existing non-profile human user.
974                                             UserInfo[] users = mUserManagerInternal.getUserInfos();
975                                             for (UserInfo userInfo : users) {
976                                                 if (!userInfo.isFull()) {
977                                                     continue;
978                                                 }
979                                                 final Pair<Integer, String> packageKey =
980                                                         Pair.create(userInfo.id, name);
981                                                 mPackageFlags.put(packageKey, flagsInt);
982                                             }
983                                         }
984                                     }
985                                 }
986                             }
987                         }
988                     }
989                     eventType = parser.next();
990                 } while (eventType != XmlPullParser.END_DOCUMENT);
991 
992                 if (writeConfigToFileNeeded) {
993                     mWriteConfigTask.schedule();
994                 }
995             }
996         } catch (XmlPullParserException e) {
997             Slog.w(TAG, "Error reading package metadata", e);
998         } catch (java.io.IOException e) {
999             if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
1000         } finally {
1001             if (fis != null) {
1002                 try {
1003                     fis.close();
1004                 } catch (java.io.IOException e1) {
1005                 }
1006             }
1007         }
1008     }
1009 }
1010