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