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