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