• 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 android.annotation.UiThread;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.os.Build;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.util.AtomicFile;
28 import android.util.DisplayMetrics;
29 import android.util.Slog;
30 import android.util.Xml;
31 
32 import com.android.internal.util.FastXmlSerializer;
33 
34 import org.xmlpull.v1.XmlPullParser;
35 import org.xmlpull.v1.XmlPullParserException;
36 import org.xmlpull.v1.XmlSerializer;
37 
38 import java.io.File;
39 import java.io.FileInputStream;
40 import java.io.FileOutputStream;
41 import java.nio.charset.StandardCharsets;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.Map;
45 
46 /**
47  * Manages warning dialogs shown during application lifecycle.
48  */
49 class AppWarnings {
50     private static final String TAG = "AppWarnings";
51     private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
52 
53     public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
54     public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
55     public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
56 
57     private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
58 
59     private final ActivityTaskManagerService mAtm;
60     private final Context mUiContext;
61     private final ConfigHandler mHandler;
62     private final UiHandler mUiHandler;
63     private final AtomicFile mConfigFile;
64 
65     private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
66     private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
67     private DeprecatedTargetSdkVersionDialog mDeprecatedTargetSdkVersionDialog;
68 
69     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
70     private HashSet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
71             new HashSet<>();
72 
73     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
alwaysShowUnsupportedCompileSdkWarning(ComponentName activity)74     void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
75         mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity);
76     }
77 
78     /**
79      * Creates a new warning dialog manager.
80      * <p>
81      * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
82      *
83      * @param atm
84      * @param uiContext
85      * @param handler
86      * @param uiHandler
87      * @param systemDir
88      */
AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, Handler uiHandler, File systemDir)89     public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
90             Handler uiHandler, File systemDir) {
91         mAtm = atm;
92         mUiContext = uiContext;
93         mHandler = new ConfigHandler(handler.getLooper());
94         mUiHandler = new UiHandler(uiHandler.getLooper());
95         mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
96 
97         readConfigFromFileAmsThread();
98     }
99 
100     /**
101      * Shows the "unsupported display size" warning, if necessary.
102      *
103      * @param r activity record for which the warning may be displayed
104      */
showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r)105     public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
106         final Configuration globalConfig = mAtm.getGlobalConfiguration();
107         if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
108                 && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
109             mUiHandler.showUnsupportedDisplaySizeDialog(r);
110         }
111     }
112 
113     /**
114      * Shows the "unsupported compile SDK" warning, if necessary.
115      *
116      * @param r activity record for which the warning may be displayed
117      */
showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r)118     public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
119         if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) {
120             // We don't know enough about this package. Abort!
121             return;
122         }
123 
124         // TODO(b/75318890): Need to move this to when the app actually crashes.
125         if (/*ActivityManager.isRunningInTestHarness()
126                 &&*/ !mAlwaysShowUnsupportedCompileSdkWarningActivities.contains(
127                         r.mActivityComponent)) {
128             // Don't show warning if we are running in a test harness and we don't have to always
129             // show for this activity.
130             return;
131         }
132 
133         // If the application was built against an pre-release SDK that's older than the current
134         // platform OR if the current platform is pre-release and older than the SDK against which
135         // the application was built OR both are pre-release with the same SDK_INT but different
136         // codenames (e.g. simultaneous pre-release development), then we're likely to run into
137         // compatibility issues. Warn the user and offer to check for an update.
138         final int compileSdk = r.appInfo.compileSdkVersion;
139         final int platformSdk = Build.VERSION.SDK_INT;
140         final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename);
141         final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
142         if ((isCompileSdkPreview && compileSdk < platformSdk)
143                 || (isPlatformSdkPreview && platformSdk < compileSdk)
144                 || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
145                     && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) {
146             mUiHandler.showUnsupportedCompileSdkDialog(r);
147         }
148     }
149 
150     /**
151      * Shows the "deprecated target sdk" warning, if necessary.
152      *
153      * @param r activity record for which the warning may be displayed
154      */
showDeprecatedTargetDialogIfNeeded(ActivityRecord r)155     public void showDeprecatedTargetDialogIfNeeded(ActivityRecord r) {
156         if (r.appInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT) {
157             mUiHandler.showDeprecatedTargetDialog(r);
158         }
159     }
160 
161     /**
162      * Called when an activity is being started.
163      *
164      * @param r record for the activity being started
165      */
onStartActivity(ActivityRecord r)166     public void onStartActivity(ActivityRecord r) {
167         showUnsupportedCompileSdkDialogIfNeeded(r);
168         showUnsupportedDisplaySizeDialogIfNeeded(r);
169         showDeprecatedTargetDialogIfNeeded(r);
170     }
171 
172     /**
173      * Called when an activity was previously started and is being resumed.
174      *
175      * @param r record for the activity being resumed
176      */
onResumeActivity(ActivityRecord r)177     public void onResumeActivity(ActivityRecord r) {
178         showUnsupportedDisplaySizeDialogIfNeeded(r);
179     }
180 
181     /**
182      * Called by ActivityManagerService when package data has been cleared.
183      *
184      * @param name the package whose data has been cleared
185      */
onPackageDataCleared(String name)186     public void onPackageDataCleared(String name) {
187         removePackageAndHideDialogs(name);
188     }
189 
190     /**
191      * Called by ActivityManagerService when a package has been uninstalled.
192      *
193      * @param name the package that has been uninstalled
194      */
onPackageUninstalled(String name)195     public void onPackageUninstalled(String name) {
196         removePackageAndHideDialogs(name);
197     }
198 
199     /**
200      * Called by ActivityManagerService when the default display density has changed.
201      */
onDensityChanged()202     public void onDensityChanged() {
203         mUiHandler.hideUnsupportedDisplaySizeDialog();
204     }
205 
206     /**
207      * Does what it says on the tin.
208      */
removePackageAndHideDialogs(String name)209     private void removePackageAndHideDialogs(String name) {
210         mUiHandler.hideDialogsForPackage(name);
211 
212         synchronized (mPackageFlags) {
213             mPackageFlags.remove(name);
214             mHandler.scheduleWrite();
215         }
216     }
217 
218     /**
219      * Hides the "unsupported display size" warning.
220      * <p>
221      * <strong>Note:</strong> Must be called on the UI thread.
222      */
223     @UiThread
hideUnsupportedDisplaySizeDialogUiThread()224     private void hideUnsupportedDisplaySizeDialogUiThread() {
225         if (mUnsupportedDisplaySizeDialog != null) {
226             mUnsupportedDisplaySizeDialog.dismiss();
227             mUnsupportedDisplaySizeDialog = null;
228         }
229     }
230 
231     /**
232      * Shows the "unsupported display size" warning for the given application.
233      * <p>
234      * <strong>Note:</strong> Must be called on the UI thread.
235      *
236      * @param ar record for the activity that triggered the warning
237      */
238     @UiThread
showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar)239     private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
240         if (mUnsupportedDisplaySizeDialog != null) {
241             mUnsupportedDisplaySizeDialog.dismiss();
242             mUnsupportedDisplaySizeDialog = null;
243         }
244         if (ar != null && !hasPackageFlag(
245                 ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
246             mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
247                     AppWarnings.this, mUiContext, ar.info.applicationInfo);
248             mUnsupportedDisplaySizeDialog.show();
249         }
250     }
251 
252     /**
253      * Shows the "unsupported compile SDK" warning for the given application.
254      * <p>
255      * <strong>Note:</strong> Must be called on the UI thread.
256      *
257      * @param ar record for the activity that triggered the warning
258      */
259     @UiThread
showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar)260     private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
261         if (mUnsupportedCompileSdkDialog != null) {
262             mUnsupportedCompileSdkDialog.dismiss();
263             mUnsupportedCompileSdkDialog = null;
264         }
265         if (ar != null && !hasPackageFlag(
266                 ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
267             mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
268                     AppWarnings.this, mUiContext, ar.info.applicationInfo);
269             mUnsupportedCompileSdkDialog.show();
270         }
271     }
272 
273     /**
274      * Shows the "deprecated target sdk version" 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
showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar)281     private void showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar) {
282         if (mDeprecatedTargetSdkVersionDialog != null) {
283             mDeprecatedTargetSdkVersionDialog.dismiss();
284             mDeprecatedTargetSdkVersionDialog = null;
285         }
286         if (ar != null && !hasPackageFlag(
287                 ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
288             mDeprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
289                     AppWarnings.this, mUiContext, ar.info.applicationInfo);
290             mDeprecatedTargetSdkVersionDialog.show();
291         }
292     }
293 
294     /**
295      * Dismisses all warnings for the given package.
296      * <p>
297      * <strong>Note:</strong> Must be called on the UI thread.
298      *
299      * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
300      *             all warnings
301      */
302     @UiThread
hideDialogsForPackageUiThread(String name)303     private void hideDialogsForPackageUiThread(String name) {
304         // Hides the "unsupported display" dialog if necessary.
305         if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
306                 mUnsupportedDisplaySizeDialog.getPackageName()))) {
307             mUnsupportedDisplaySizeDialog.dismiss();
308             mUnsupportedDisplaySizeDialog = null;
309         }
310 
311         // Hides the "unsupported compile SDK" dialog if necessary.
312         if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
313                 mUnsupportedCompileSdkDialog.getPackageName()))) {
314             mUnsupportedCompileSdkDialog.dismiss();
315             mUnsupportedCompileSdkDialog = null;
316         }
317 
318         // Hides the "deprecated target sdk version" dialog if necessary.
319         if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
320                 mDeprecatedTargetSdkVersionDialog.getPackageName()))) {
321             mDeprecatedTargetSdkVersionDialog.dismiss();
322             mDeprecatedTargetSdkVersionDialog = null;
323         }
324     }
325 
326     /**
327      * Returns the value of the flag for the given package.
328      *
329      * @param name the package from which to retrieve the flag
330      * @param flag the bitmask for the flag to retrieve
331      * @return {@code true} if the flag is enabled, {@code false} otherwise
332      */
hasPackageFlag(String name, int flag)333     boolean hasPackageFlag(String name, int flag) {
334         return (getPackageFlags(name) & flag) == flag;
335     }
336 
337     /**
338      * Sets the flag for the given package to the specified value.
339      *
340      * @param name the package on which to set the flag
341      * @param flag the bitmask for flag to set
342      * @param enabled the value to set for the flag
343      */
setPackageFlag(String name, int flag, boolean enabled)344     void setPackageFlag(String name, int flag, boolean enabled) {
345         synchronized (mPackageFlags) {
346             final int curFlags = getPackageFlags(name);
347             final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag);
348             if (curFlags != newFlags) {
349                 if (newFlags != 0) {
350                     mPackageFlags.put(name, newFlags);
351                 } else {
352                     mPackageFlags.remove(name);
353                 }
354                 mHandler.scheduleWrite();
355             }
356         }
357     }
358 
359     /**
360      * Returns the bitmask of flags set for the specified package.
361      */
getPackageFlags(String name)362     private int getPackageFlags(String name) {
363         synchronized (mPackageFlags) {
364             return mPackageFlags.getOrDefault(name, 0);
365         }
366     }
367 
368     /**
369      * Handles messages on the system process UI thread.
370      */
371     private final class UiHandler extends Handler {
372         private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
373         private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
374         private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
375         private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
376         private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
377 
UiHandler(Looper looper)378         public UiHandler(Looper looper) {
379             super(looper, null, true);
380         }
381 
382         @Override
handleMessage(Message msg)383         public void handleMessage(Message msg) {
384             switch (msg.what) {
385                 case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
386                     final ActivityRecord ar = (ActivityRecord) msg.obj;
387                     showUnsupportedDisplaySizeDialogUiThread(ar);
388                 } break;
389                 case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
390                     hideUnsupportedDisplaySizeDialogUiThread();
391                 } break;
392                 case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
393                     final ActivityRecord ar = (ActivityRecord) msg.obj;
394                     showUnsupportedCompileSdkDialogUiThread(ar);
395                 } break;
396                 case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
397                     final String name = (String) msg.obj;
398                     hideDialogsForPackageUiThread(name);
399                 } break;
400                 case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: {
401                     final ActivityRecord ar = (ActivityRecord) msg.obj;
402                     showDeprecatedTargetSdkDialogUiThread(ar);
403                 } break;
404             }
405         }
406 
showUnsupportedDisplaySizeDialog(ActivityRecord r)407         public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
408             removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
409             obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
410         }
411 
hideUnsupportedDisplaySizeDialog()412         public void hideUnsupportedDisplaySizeDialog() {
413             removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
414             sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
415         }
416 
showUnsupportedCompileSdkDialog(ActivityRecord r)417         public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
418             removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
419             obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
420         }
421 
showDeprecatedTargetDialog(ActivityRecord r)422         public void showDeprecatedTargetDialog(ActivityRecord r) {
423             removeMessages(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG);
424             obtainMessage(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG, r).sendToTarget();
425         }
426 
hideDialogsForPackage(String name)427         public void hideDialogsForPackage(String name) {
428             obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
429         }
430     }
431 
432     /**
433      * Handles messages on the ActivityTaskManagerService thread.
434      */
435     private final class ConfigHandler extends Handler {
436         private static final int MSG_WRITE = 1;
437 
438         private static final int DELAY_MSG_WRITE = 10000;
439 
ConfigHandler(Looper looper)440         public ConfigHandler(Looper looper) {
441             super(looper, null, true);
442         }
443 
444         @Override
handleMessage(Message msg)445         public void handleMessage(Message msg) {
446             switch (msg.what) {
447                 case MSG_WRITE:
448                     writeConfigToFileAmsThread();
449                     break;
450             }
451         }
452 
scheduleWrite()453         public void scheduleWrite() {
454             removeMessages(MSG_WRITE);
455             sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
456         }
457     }
458 
459     /**
460      * Writes the configuration file.
461      * <p>
462      * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
463      * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
464      */
writeConfigToFileAmsThread()465     private void writeConfigToFileAmsThread() {
466         // Create a shallow copy so that we don't have to synchronize on config.
467         final HashMap<String, Integer> packageFlags;
468         synchronized (mPackageFlags) {
469             packageFlags = new HashMap<>(mPackageFlags);
470         }
471 
472         FileOutputStream fos = null;
473         try {
474             fos = mConfigFile.startWrite();
475 
476             final XmlSerializer out = new FastXmlSerializer();
477             out.setOutput(fos, StandardCharsets.UTF_8.name());
478             out.startDocument(null, true);
479             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
480             out.startTag(null, "packages");
481 
482             for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
483                 String pkg = entry.getKey();
484                 int mode = entry.getValue();
485                 if (mode == 0) {
486                     continue;
487                 }
488                 out.startTag(null, "package");
489                 out.attribute(null, "name", pkg);
490                 out.attribute(null, "flags", Integer.toString(mode));
491                 out.endTag(null, "package");
492             }
493 
494             out.endTag(null, "packages");
495             out.endDocument();
496 
497             mConfigFile.finishWrite(fos);
498         } catch (java.io.IOException e1) {
499             Slog.w(TAG, "Error writing package metadata", e1);
500             if (fos != null) {
501                 mConfigFile.failWrite(fos);
502             }
503         }
504     }
505 
506     /**
507      * Reads the configuration file and populates the package flags.
508      * <p>
509      * <strong>Note:</strong> Must be called from the constructor (and thus on the
510      * ActivityManagerService thread) since we don't synchronize on config.
511      */
readConfigFromFileAmsThread()512     private void readConfigFromFileAmsThread() {
513         FileInputStream fis = null;
514 
515         try {
516             fis = mConfigFile.openRead();
517 
518             final XmlPullParser parser = Xml.newPullParser();
519             parser.setInput(fis, StandardCharsets.UTF_8.name());
520 
521             int eventType = parser.getEventType();
522             while (eventType != XmlPullParser.START_TAG &&
523                     eventType != XmlPullParser.END_DOCUMENT) {
524                 eventType = parser.next();
525             }
526             if (eventType == XmlPullParser.END_DOCUMENT) {
527                 return;
528             }
529 
530             String tagName = parser.getName();
531             if ("packages".equals(tagName)) {
532                 eventType = parser.next();
533                 do {
534                     if (eventType == XmlPullParser.START_TAG) {
535                         tagName = parser.getName();
536                         if (parser.getDepth() == 2) {
537                             if ("package".equals(tagName)) {
538                                 final String name = parser.getAttributeValue(null, "name");
539                                 if (name != null) {
540                                     final String flags = parser.getAttributeValue(
541                                             null, "flags");
542                                     int flagsInt = 0;
543                                     if (flags != null) {
544                                         try {
545                                             flagsInt = Integer.parseInt(flags);
546                                         } catch (NumberFormatException e) {
547                                         }
548                                     }
549                                     mPackageFlags.put(name, flagsInt);
550                                 }
551                             }
552                         }
553                     }
554                     eventType = parser.next();
555                 } while (eventType != XmlPullParser.END_DOCUMENT);
556             }
557         } catch (XmlPullParserException e) {
558             Slog.w(TAG, "Error reading package metadata", e);
559         } catch (java.io.IOException e) {
560             if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
561         } finally {
562             if (fis != null) {
563                 try {
564                     fis.close();
565                 } catch (java.io.IOException e1) {
566                 }
567             }
568         }
569     }
570 }
571