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