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 android.annotation.NonNull;
20 import android.annotation.UiThread;
21 import android.annotation.WorkerThread;
22 import android.app.AlertDialog;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.ApplicationInfo;
29 import android.content.res.Configuration;
30 import android.os.Build;
31 import android.os.Handler;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.os.SystemProperties;
35 import android.util.ArrayMap;
36 import android.util.ArraySet;
37 import android.util.AtomicFile;
38 import android.util.DisplayMetrics;
39 import android.util.Slog;
40 import android.util.Xml;
41 
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.internal.util.ArrayUtils;
44 import com.android.modules.utils.TypedXmlPullParser;
45 import com.android.modules.utils.TypedXmlSerializer;
46 import com.android.server.IoThread;
47 
48 import org.xmlpull.v1.XmlPullParser;
49 import org.xmlpull.v1.XmlPullParserException;
50 
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.FileOutputStream;
54 import java.util.concurrent.atomic.AtomicReference;
55 
56 /**
57  * Manages warning dialogs shown during application lifecycle.
58  */
59 class AppWarnings {
60     private static final String TAG = "AppWarnings";
61     private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
62 
63     public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
64     public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
65     public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
66     public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
67 
68     @GuardedBy("mPackageFlags")
69     private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>();
70 
71     private final ActivityTaskManagerService mAtm;
72     private final Context mUiContext;
73     private final WriteConfigTask mWriteConfigTask;
74     private final UiHandler mUiHandler;
75     private final AtomicFile mConfigFile;
76 
77     private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
78     private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
79     private DeprecatedTargetSdkVersionDialog mDeprecatedTargetSdkVersionDialog;
80     private DeprecatedAbiDialog mDeprecatedAbiDialog;
81 
82     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
83     private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
84             new ArraySet<>();
85 
86     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
alwaysShowUnsupportedCompileSdkWarning(ComponentName activity)87     void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
88         mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity);
89     }
90 
91     /** Creates a new warning dialog manager. */
AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, Handler uiHandler, File systemDir)92     public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
93             Handler uiHandler, File systemDir) {
94         mAtm = atm;
95         mUiContext = uiContext;
96         mWriteConfigTask = new WriteConfigTask();
97         mUiHandler = new UiHandler(uiHandler.getLooper());
98         mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
99 
100         readConfigFromFileAmsThread();
101     }
102 
103     /**
104      * Shows the "unsupported display size" warning, if necessary.
105      *
106      * @param r activity record for which the warning may be displayed
107      */
showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r)108     public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
109         final Configuration globalConfig = mAtm.getGlobalConfiguration();
110         if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
111                 && r.info.applicationInfo.requiresSmallestWidthDp
112                 > globalConfig.smallestScreenWidthDp) {
113             mUiHandler.showUnsupportedDisplaySizeDialog(r);
114         }
115     }
116 
117     /**
118      * Shows the "unsupported compile SDK" warning, if necessary.
119      *
120      * @param r activity record for which the warning may be displayed
121      */
showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r)122     public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
123         if (r.info.applicationInfo.compileSdkVersion == 0
124                 || r.info.applicationInfo.compileSdkVersionCodename == null) {
125             // We don't know enough about this package. Abort!
126             return;
127         }
128 
129         // TODO(b/75318890): Need to move this to when the app actually crashes.
130         if (/*ActivityManager.isRunningInTestHarness()
131                 &&*/ !mAlwaysShowUnsupportedCompileSdkWarningActivities.contains(
132                         r.mActivityComponent)) {
133             // Don't show warning if we are running in a test harness and we don't have to always
134             // show for this activity.
135             return;
136         }
137 
138         // If the application was built against an pre-release SDK that's older than the current
139         // platform OR if the current platform is pre-release and older than the SDK against which
140         // the application was built OR both are pre-release with the same SDK_INT but different
141         // codenames (e.g. simultaneous pre-release development), then we're likely to run into
142         // compatibility issues. Warn the user and offer to check for an update.
143         final int compileSdk = r.info.applicationInfo.compileSdkVersion;
144         final int platformSdk = Build.VERSION.SDK_INT;
145         final boolean isCompileSdkPreview =
146                 !"REL".equals(r.info.applicationInfo.compileSdkVersionCodename);
147         final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
148         if ((isCompileSdkPreview && compileSdk < platformSdk)
149                 || (isPlatformSdkPreview && platformSdk < compileSdk)
150                 || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
151                     && !Build.VERSION.CODENAME.equals(
152                             r.info.applicationInfo.compileSdkVersionCodename))) {
153             mUiHandler.showUnsupportedCompileSdkDialog(r);
154         }
155     }
156 
157     /**
158      * Shows the "deprecated target sdk" warning, if necessary.
159      *
160      * @param r activity record for which the warning may be displayed
161      */
showDeprecatedTargetDialogIfNeeded(ActivityRecord r)162     public void showDeprecatedTargetDialogIfNeeded(ActivityRecord r) {
163         if (r.info.applicationInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT) {
164             mUiHandler.showDeprecatedTargetDialog(r);
165         }
166     }
167 
168     /**
169      * Shows the "deprecated abi" warning, if necessary. This can only happen is the device
170      * supports both 64-bit and 32-bit ABIs, and the app only contains 32-bit libraries. The app
171      * cannot be installed if the device only supports 64-bit ABI while the app contains only 32-bit
172      * libraries.
173      *
174      * @param r activity record for which the warning may be displayed
175      */
showDeprecatedAbiDialogIfNeeded(ActivityRecord r)176     public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) {
177         final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt
178                 & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0;
179         if (isUsingAbiOverride) {
180             // The abiOverride flag was specified during installation, which means that if the app
181             // is currently running in 32-bit mode, it is intended. Do not show the warning dialog.
182             return;
183         }
184         // The warning dialog can also be disabled for debugging purpose
185         final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean(
186                 "debug.wm.disable_deprecated_abi_dialog", false);
187         if (disableDeprecatedAbiDialog) {
188             return;
189         }
190         final String appPrimaryAbi = r.info.applicationInfo.primaryCpuAbi;
191         final String appSecondaryAbi = r.info.applicationInfo.secondaryCpuAbi;
192         final boolean appContainsOnly32bitLibraries =
193                 (appPrimaryAbi != null && appSecondaryAbi == null && !appPrimaryAbi.contains("64"));
194         final boolean is64BitDevice =
195                 ArrayUtils.find(Build.SUPPORTED_ABIS, abi -> abi.contains("64")) != null;
196         if (is64BitDevice && appContainsOnly32bitLibraries) {
197             mUiHandler.showDeprecatedAbiDialog(r);
198         }
199     }
200 
201     /**
202      * Called when an activity is being started.
203      *
204      * @param r record for the activity being started
205      */
onStartActivity(ActivityRecord r)206     public void onStartActivity(ActivityRecord r) {
207         showUnsupportedCompileSdkDialogIfNeeded(r);
208         showUnsupportedDisplaySizeDialogIfNeeded(r);
209         showDeprecatedTargetDialogIfNeeded(r);
210         showDeprecatedAbiDialogIfNeeded(r);
211     }
212 
213     /**
214      * Called when an activity was previously started and is being resumed.
215      *
216      * @param r record for the activity being resumed
217      */
onResumeActivity(ActivityRecord r)218     public void onResumeActivity(ActivityRecord r) {
219         showUnsupportedDisplaySizeDialogIfNeeded(r);
220     }
221 
222     /**
223      * Called by ActivityManagerService when package data has been cleared.
224      *
225      * @param name the package whose data has been cleared
226      */
onPackageDataCleared(String name)227     public void onPackageDataCleared(String name) {
228         removePackageAndHideDialogs(name);
229     }
230 
231     /**
232      * Called by ActivityManagerService when a package has been uninstalled.
233      *
234      * @param name the package that has been uninstalled
235      */
onPackageUninstalled(String name)236     public void onPackageUninstalled(String name) {
237         removePackageAndHideDialogs(name);
238     }
239 
240     /**
241      * Called by ActivityManagerService when the default display density has changed.
242      */
onDensityChanged()243     public void onDensityChanged() {
244         mUiHandler.hideUnsupportedDisplaySizeDialog();
245     }
246 
247     /**
248      * Does what it says on the tin.
249      */
removePackageAndHideDialogs(String name)250     private void removePackageAndHideDialogs(String name) {
251         mUiHandler.hideDialogsForPackage(name);
252 
253         synchronized (mPackageFlags) {
254             if (mPackageFlags.remove(name) != null) {
255                 mWriteConfigTask.schedule();
256             }
257         }
258     }
259 
260     /**
261      * Hides the "unsupported display size" warning.
262      * <p>
263      * <strong>Note:</strong> Must be called on the UI thread.
264      */
265     @UiThread
hideUnsupportedDisplaySizeDialogUiThread()266     private void hideUnsupportedDisplaySizeDialogUiThread() {
267         if (mUnsupportedDisplaySizeDialog != null) {
268             mUnsupportedDisplaySizeDialog.dismiss();
269             mUnsupportedDisplaySizeDialog = null;
270         }
271     }
272 
273     /**
274      * Shows the "unsupported display size" warning for the given application.
275      * <p>
276      * <strong>Note:</strong> Must be called on the UI thread.
277      *
278      * @param ar record for the activity that triggered the warning
279      */
280     @UiThread
showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar)281     private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
282         if (mUnsupportedDisplaySizeDialog != null) {
283             mUnsupportedDisplaySizeDialog.dismiss();
284             mUnsupportedDisplaySizeDialog = null;
285         }
286         if (ar != null && !hasPackageFlag(
287                 ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
288             mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
289                     AppWarnings.this, mUiContext, ar.info.applicationInfo);
290             mUnsupportedDisplaySizeDialog.show();
291         }
292     }
293 
294     /**
295      * Shows the "unsupported compile SDK" warning for the given application.
296      * <p>
297      * <strong>Note:</strong> Must be called on the UI thread.
298      *
299      * @param ar record for the activity that triggered the warning
300      */
301     @UiThread
showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar)302     private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
303         if (mUnsupportedCompileSdkDialog != null) {
304             mUnsupportedCompileSdkDialog.dismiss();
305             mUnsupportedCompileSdkDialog = null;
306         }
307         if (ar != null && !hasPackageFlag(
308                 ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
309             mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
310                     AppWarnings.this, mUiContext, ar.info.applicationInfo);
311             mUnsupportedCompileSdkDialog.show();
312         }
313     }
314 
315     /**
316      * Shows the "deprecated target sdk version" warning for the given application.
317      * <p>
318      * <strong>Note:</strong> Must be called on the UI thread.
319      *
320      * @param ar record for the activity that triggered the warning
321      */
322     @UiThread
showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar)323     private void showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar) {
324         if (mDeprecatedTargetSdkVersionDialog != null) {
325             mDeprecatedTargetSdkVersionDialog.dismiss();
326             mDeprecatedTargetSdkVersionDialog = null;
327         }
328         if (ar != null && !hasPackageFlag(
329                 ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
330             mDeprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
331                     AppWarnings.this, mUiContext, ar.info.applicationInfo);
332             mDeprecatedTargetSdkVersionDialog.show();
333         }
334     }
335 
336     /**
337      * Shows the "deprecated abi" warning for the given application.
338      * <p>
339      * <strong>Note:</strong> Must be called on the UI thread.
340      *
341      * @param ar record for the activity that triggered the warning
342      */
343     @UiThread
showDeprecatedAbiDialogUiThread(ActivityRecord ar)344     private void showDeprecatedAbiDialogUiThread(ActivityRecord ar) {
345         if (mDeprecatedAbiDialog != null) {
346             mDeprecatedAbiDialog.dismiss();
347             mDeprecatedAbiDialog = null;
348         }
349         if (ar != null && !hasPackageFlag(
350                 ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) {
351             mDeprecatedAbiDialog = new DeprecatedAbiDialog(
352                     AppWarnings.this, mUiContext, ar.info.applicationInfo);
353             mDeprecatedAbiDialog.show();
354         }
355     }
356 
357     /**
358      * Dismisses all warnings for the given package.
359      * <p>
360      * <strong>Note:</strong> Must be called on the UI thread.
361      *
362      * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
363      *             all warnings
364      */
365     @UiThread
hideDialogsForPackageUiThread(String name)366     private void hideDialogsForPackageUiThread(String name) {
367         // Hides the "unsupported display" dialog if necessary.
368         if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
369                 mUnsupportedDisplaySizeDialog.mPackageName))) {
370             mUnsupportedDisplaySizeDialog.dismiss();
371             mUnsupportedDisplaySizeDialog = null;
372         }
373 
374         // Hides the "unsupported compile SDK" dialog if necessary.
375         if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
376                 mUnsupportedCompileSdkDialog.mPackageName))) {
377             mUnsupportedCompileSdkDialog.dismiss();
378             mUnsupportedCompileSdkDialog = null;
379         }
380 
381         // Hides the "deprecated target sdk version" dialog if necessary.
382         if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
383                 mDeprecatedTargetSdkVersionDialog.mPackageName))) {
384             mDeprecatedTargetSdkVersionDialog.dismiss();
385             mDeprecatedTargetSdkVersionDialog = null;
386         }
387 
388         // Hides the "deprecated abi" dialog if necessary.
389         if (mDeprecatedAbiDialog != null && (name == null || name.equals(
390                 mDeprecatedAbiDialog.mPackageName))) {
391             mDeprecatedAbiDialog.dismiss();
392             mDeprecatedAbiDialog = null;
393         }
394     }
395 
396     /**
397      * Returns the value of the flag for the given package.
398      *
399      * @param name the package from which to retrieve the flag
400      * @param flag the bitmask for the flag to retrieve
401      * @return {@code true} if the flag is enabled, {@code false} otherwise
402      */
hasPackageFlag(String name, int flag)403     boolean hasPackageFlag(String name, int flag) {
404         return (getPackageFlags(name) & flag) == flag;
405     }
406 
407     /**
408      * Sets the flag for the given package to the specified value.
409      *
410      * @param name the package on which to set the flag
411      * @param flag the bitmask for flag to set
412      * @param enabled the value to set for the flag
413      */
setPackageFlag(String name, int flag, boolean enabled)414     void setPackageFlag(String name, int flag, boolean enabled) {
415         synchronized (mPackageFlags) {
416             final int curFlags = getPackageFlags(name);
417             final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag);
418             if (curFlags != newFlags) {
419                 if (newFlags != 0) {
420                     mPackageFlags.put(name, newFlags);
421                 } else {
422                     mPackageFlags.remove(name);
423                 }
424                 mWriteConfigTask.schedule();
425             }
426         }
427     }
428 
429     /**
430      * Returns the bitmask of flags set for the specified package.
431      */
getPackageFlags(String name)432     private int getPackageFlags(String name) {
433         synchronized (mPackageFlags) {
434             return mPackageFlags.getOrDefault(name, 0);
435         }
436     }
437 
438     /**
439      * Handles messages on the system process UI thread.
440      */
441     private final class UiHandler extends Handler {
442         private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
443         private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
444         private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
445         private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
446         private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
447         private static final int MSG_SHOW_DEPRECATED_ABI_DIALOG = 6;
448 
UiHandler(Looper looper)449         public UiHandler(Looper looper) {
450             super(looper, null, true);
451         }
452 
453         @Override
handleMessage(Message msg)454         public void handleMessage(Message msg) {
455             switch (msg.what) {
456                 case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
457                     final ActivityRecord ar = (ActivityRecord) msg.obj;
458                     showUnsupportedDisplaySizeDialogUiThread(ar);
459                 } break;
460                 case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
461                     hideUnsupportedDisplaySizeDialogUiThread();
462                 } break;
463                 case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
464                     final ActivityRecord ar = (ActivityRecord) msg.obj;
465                     showUnsupportedCompileSdkDialogUiThread(ar);
466                 } break;
467                 case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
468                     final String name = (String) msg.obj;
469                     hideDialogsForPackageUiThread(name);
470                 } break;
471                 case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: {
472                     final ActivityRecord ar = (ActivityRecord) msg.obj;
473                     showDeprecatedTargetSdkDialogUiThread(ar);
474                 } break;
475                 case MSG_SHOW_DEPRECATED_ABI_DIALOG: {
476                     final ActivityRecord ar = (ActivityRecord) msg.obj;
477                     showDeprecatedAbiDialogUiThread(ar);
478                 } break;
479             }
480         }
481 
showUnsupportedDisplaySizeDialog(ActivityRecord r)482         public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
483             removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
484             obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
485         }
486 
hideUnsupportedDisplaySizeDialog()487         public void hideUnsupportedDisplaySizeDialog() {
488             removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
489             sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
490         }
491 
showUnsupportedCompileSdkDialog(ActivityRecord r)492         public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
493             removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
494             obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
495         }
496 
showDeprecatedTargetDialog(ActivityRecord r)497         public void showDeprecatedTargetDialog(ActivityRecord r) {
498             removeMessages(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG);
499             obtainMessage(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG, r).sendToTarget();
500         }
501 
showDeprecatedAbiDialog(ActivityRecord r)502         public void showDeprecatedAbiDialog(ActivityRecord r) {
503             removeMessages(MSG_SHOW_DEPRECATED_ABI_DIALOG);
504             obtainMessage(MSG_SHOW_DEPRECATED_ABI_DIALOG, r).sendToTarget();
505         }
506 
hideDialogsForPackage(String name)507         public void hideDialogsForPackage(String name) {
508             obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
509         }
510     }
511 
512     static class BaseDialog {
513         final AppWarnings mManager;
514         final String mPackageName;
515         AlertDialog mDialog;
516         private BroadcastReceiver mCloseReceiver;
517 
BaseDialog(AppWarnings manager, String packageName)518         BaseDialog(AppWarnings manager, String packageName) {
519             mManager = manager;
520             mPackageName = packageName;
521         }
522 
523         @UiThread
show()524         void show() {
525             if (mDialog == null) return;
526             if (mCloseReceiver == null) {
527                 mCloseReceiver = new BroadcastReceiver() {
528                     @Override
529                     public void onReceive(Context context, Intent intent) {
530                         if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
531                             mManager.mUiHandler.hideDialogsForPackage(mPackageName);
532                         }
533                     }
534                 };
535                 mManager.mUiContext.registerReceiver(mCloseReceiver,
536                         new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
537                         Context.RECEIVER_EXPORTED);
538             }
539             Slog.w(TAG, "Showing " + getClass().getSimpleName() + " for package " + mPackageName);
540             mDialog.show();
541         }
542 
543         @UiThread
dismiss()544         void dismiss() {
545             if (mDialog == null) return;
546             if (mCloseReceiver != null) {
547                 mManager.mUiContext.unregisterReceiver(mCloseReceiver);
548                 mCloseReceiver = null;
549             }
550             mDialog.dismiss();
551             mDialog = null;
552         }
553     }
554 
555     private final class WriteConfigTask implements Runnable {
556         private static final long WRITE_CONFIG_DELAY_MS = 10000;
557         final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags =
558                 new AtomicReference<>();
559 
560         @Override
run()561         public void run() {
562             final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null);
563             if (packageFlags != null) {
564                 writeConfigToFile(packageFlags);
565             }
566         }
567 
568         @GuardedBy("mPackageFlags")
schedule()569         void schedule() {
570             if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) {
571                 IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS);
572             }
573         }
574     }
575 
576     /** Writes the configuration file. */
577     @WorkerThread
writeConfigToFile(@onNull ArrayMap<String, Integer> packageFlags)578     private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) {
579         FileOutputStream fos = null;
580         try {
581             fos = mConfigFile.startWrite();
582 
583             final TypedXmlSerializer out = Xml.resolveSerializer(fos);
584             out.startDocument(null, true);
585             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
586             out.startTag(null, "packages");
587 
588             for (int i = 0; i < packageFlags.size(); i++) {
589                 final String pkg = packageFlags.keyAt(i);
590                 final int mode = packageFlags.valueAt(i);
591                 if (mode == 0) {
592                     continue;
593                 }
594                 out.startTag(null, "package");
595                 out.attribute(null, "name", pkg);
596                 out.attributeInt(null, "flags", mode);
597                 out.endTag(null, "package");
598             }
599 
600             out.endTag(null, "packages");
601             out.endDocument();
602 
603             mConfigFile.finishWrite(fos);
604         } catch (java.io.IOException e1) {
605             Slog.w(TAG, "Error writing package metadata", e1);
606             if (fos != null) {
607                 mConfigFile.failWrite(fos);
608             }
609         }
610     }
611 
612     /**
613      * Reads the configuration file and populates the package flags.
614      * <p>
615      * <strong>Note:</strong> Must be called from the constructor (and thus on the
616      * ActivityManagerService thread) since we don't synchronize on config.
617      */
readConfigFromFileAmsThread()618     private void readConfigFromFileAmsThread() {
619         FileInputStream fis = null;
620 
621         try {
622             fis = mConfigFile.openRead();
623 
624             final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
625 
626             int eventType = parser.getEventType();
627             while (eventType != XmlPullParser.START_TAG &&
628                     eventType != XmlPullParser.END_DOCUMENT) {
629                 eventType = parser.next();
630             }
631             if (eventType == XmlPullParser.END_DOCUMENT) {
632                 return;
633             }
634 
635             String tagName = parser.getName();
636             if ("packages".equals(tagName)) {
637                 eventType = parser.next();
638                 do {
639                     if (eventType == XmlPullParser.START_TAG) {
640                         tagName = parser.getName();
641                         if (parser.getDepth() == 2) {
642                             if ("package".equals(tagName)) {
643                                 final String name = parser.getAttributeValue(null, "name");
644                                 if (name != null) {
645                                     int flagsInt = parser.getAttributeInt(null, "flags", 0);
646                                     mPackageFlags.put(name, flagsInt);
647                                 }
648                             }
649                         }
650                     }
651                     eventType = parser.next();
652                 } while (eventType != XmlPullParser.END_DOCUMENT);
653             }
654         } catch (XmlPullParserException e) {
655             Slog.w(TAG, "Error reading package metadata", e);
656         } catch (java.io.IOException e) {
657             if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
658         } finally {
659             if (fis != null) {
660                 try {
661                     fis.close();
662                 } catch (java.io.IOException e1) {
663                 }
664             }
665         }
666     }
667 }
668