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 static android.os.UserHandle.USER_NULL; 20 import static android.os.UserHandle.USER_SYSTEM; 21 import static android.os.UserManager.isHeadlessSystemUserMode; 22 import static android.os.UserManager.isVisibleBackgroundUsersEnabled; 23 24 import android.annotation.NonNull; 25 import android.annotation.UiThread; 26 import android.annotation.UserIdInt; 27 import android.annotation.WorkerThread; 28 import android.app.AlertDialog; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.Flags; 36 import android.content.pm.UserInfo; 37 import android.content.res.Configuration; 38 import android.os.Build; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.SystemProperties; 43 import android.os.UserHandle; 44 import android.system.Os; 45 import android.system.OsConstants; 46 import android.util.ArrayMap; 47 import android.util.ArraySet; 48 import android.util.AtomicFile; 49 import android.util.DisplayMetrics; 50 import android.util.Pair; 51 import android.util.Slog; 52 import android.util.SparseArray; 53 import android.util.Xml; 54 import android.view.ContextThemeWrapper; 55 56 import com.android.internal.annotations.GuardedBy; 57 import com.android.internal.util.ArrayUtils; 58 import com.android.modules.utils.TypedXmlPullParser; 59 import com.android.modules.utils.TypedXmlSerializer; 60 import com.android.server.IoThread; 61 import com.android.server.LocalServices; 62 import com.android.server.pm.UserManagerInternal; 63 64 import org.xmlpull.v1.XmlPullParser; 65 import org.xmlpull.v1.XmlPullParserException; 66 67 import java.io.File; 68 import java.io.FileInputStream; 69 import java.io.FileOutputStream; 70 import java.util.concurrent.atomic.AtomicReference; 71 72 /** 73 * Manages warning dialogs shown during application lifecycle. 74 */ 75 class AppWarnings { 76 private static final String TAG = "AppWarnings"; 77 private static final String CONFIG_FILE_NAME = "packages-warnings.xml"; 78 79 public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01; 80 public static final int FLAG_HIDE_COMPILE_SDK = 0x02; 81 public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04; 82 public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08; 83 public static final int FLAG_HIDE_PAGE_SIZE_MISMATCH = 0x10; 84 85 /** 86 * Map of package flags for each user. 87 * Key: {@literal Pair<userId, packageName>} 88 * Value: Flags 89 */ 90 @GuardedBy("mPackageFlags") 91 private final ArrayMap<Pair<Integer, String>, Integer> mPackageFlags = new ArrayMap<>(); 92 93 private final ActivityTaskManagerService mAtm; 94 private final WriteConfigTask mWriteConfigTask; 95 private final UiHandler mUiHandler; 96 private final AtomicFile mConfigFile; 97 98 private UserManagerInternal mUserManagerInternal; 99 100 /** 101 * Maps of app warning dialogs for each user. 102 * Key: userId 103 * Value: The warning dialog for specific user 104 */ 105 private SparseArray<UnsupportedDisplaySizeDialog> mUnsupportedDisplaySizeDialogs; 106 private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs; 107 private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs; 108 private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs; 109 private SparseArray<PageSizeMismatchDialog> mPageSizeMismatchDialogs; 110 111 /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ 112 private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities = 113 new ArraySet<>(); 114 115 /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ alwaysShowUnsupportedCompileSdkWarning(ComponentName activity)116 void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) { 117 mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity); 118 } 119 120 /** Creates a new warning dialog manager. */ AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, Handler uiHandler, File systemDir)121 public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, 122 Handler uiHandler, File systemDir) { 123 mAtm = atm; 124 mWriteConfigTask = new WriteConfigTask(); 125 mUiHandler = new UiHandler(uiHandler.getLooper()); 126 mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config"); 127 } 128 129 /** 130 * Called when ActivityManagerService receives its systemReady call during boot. 131 */ onSystemReady()132 void onSystemReady() { 133 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 134 readConfigFromFileAmsThread(); 135 136 if (!isVisibleBackgroundUsersEnabled()) { 137 return; 138 } 139 140 mUserManagerInternal.addUserLifecycleListener( 141 new UserManagerInternal.UserLifecycleListener() { 142 @Override 143 public void onUserRemoved(UserInfo user) { 144 // Ignore profile user. 145 if (!user.isFull()) { 146 return; 147 } 148 // Dismiss all warnings and clear all package flags for the user. 149 mUiHandler.hideDialogsForPackage(/* name= */ null, user.id); 150 clearAllPackageFlagsForUser(user.id); 151 } 152 }); 153 } 154 155 /** 156 * Shows the "unsupported display size" warning, if necessary. 157 * 158 * @param r activity record for which the warning may be displayed 159 */ showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r)160 public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) { 161 final DisplayContent dc = r.getDisplayContent(); 162 final Configuration config = dc == null 163 ? mAtm.getGlobalConfiguration() : dc.getConfiguration(); 164 if (config.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE 165 && r.info.applicationInfo.requiresSmallestWidthDp 166 > config.smallestScreenWidthDp) { 167 mUiHandler.showUnsupportedDisplaySizeDialog(r); 168 } 169 } 170 171 /** 172 * Shows the "unsupported compile SDK" warning, if necessary. 173 * 174 * @param r activity record for which the warning may be displayed 175 */ showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r)176 public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) { 177 if (r.info.applicationInfo.compileSdkVersion == 0 178 || r.info.applicationInfo.compileSdkVersionCodename == null) { 179 // We don't know enough about this package. Abort! 180 return; 181 } 182 183 // TODO(b/75318890): Need to move this to when the app actually crashes. 184 if (/*ActivityManager.isRunningInTestHarness() 185 &&*/ !mAlwaysShowUnsupportedCompileSdkWarningActivities.contains( 186 r.mActivityComponent)) { 187 // Don't show warning if we are running in a test harness and we don't have to always 188 // show for this activity. 189 return; 190 } 191 192 // If the application was built against an pre-release SDK that's older than the current 193 // platform OR if the current platform is pre-release and older than the SDK against which 194 // the application was built OR both are pre-release with the same SDK_INT but different 195 // codenames (e.g. simultaneous pre-release development), then we're likely to run into 196 // compatibility issues. Warn the user and offer to check for an update. 197 final int compileSdk = r.info.applicationInfo.compileSdkVersion; 198 final int platformSdk = Build.VERSION.SDK_INT; 199 final boolean isCompileSdkPreview = 200 !"REL".equals(r.info.applicationInfo.compileSdkVersionCodename); 201 final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME); 202 if ((isCompileSdkPreview && compileSdk < platformSdk) 203 || (isPlatformSdkPreview && platformSdk < compileSdk) 204 || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk 205 && !Build.VERSION.CODENAME.equals( 206 r.info.applicationInfo.compileSdkVersionCodename))) { 207 mUiHandler.showUnsupportedCompileSdkDialog(r); 208 } 209 } 210 211 /** 212 * Shows the "deprecated target sdk" warning, if necessary. 213 * 214 * @param r activity record for which the warning may be displayed 215 */ showDeprecatedTargetDialogIfNeeded(ActivityRecord r)216 public void showDeprecatedTargetDialogIfNeeded(ActivityRecord r) { 217 // The warning dialog can be disabled for debugging or testing purposes 218 final boolean disableDeprecatedTargetSdkDialog = SystemProperties.getBoolean( 219 "debug.wm.disable_deprecated_target_sdk_dialog", false); 220 if (r.info.applicationInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT 221 && !disableDeprecatedTargetSdkDialog) { 222 mUiHandler.showDeprecatedTargetDialog(r); 223 } 224 } 225 226 /** 227 * Shows the "deprecated abi" warning, if necessary. This can only happen is the device 228 * supports both 64-bit and 32-bit ABIs, and the app only contains 32-bit libraries. The app 229 * cannot be installed if the device only supports 64-bit ABI while the app contains only 32-bit 230 * libraries. 231 * 232 * @param r activity record for which the warning may be displayed 233 */ showDeprecatedAbiDialogIfNeeded(ActivityRecord r)234 public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) { 235 final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt 236 & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0; 237 if (isUsingAbiOverride) { 238 // The abiOverride flag was specified during installation, which means that if the app 239 // is currently running in 32-bit mode, it is intended. Do not show the warning dialog. 240 return; 241 } 242 // The warning dialog can also be disabled for debugging purpose 243 final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean( 244 "debug.wm.disable_deprecated_abi_dialog", false); 245 if (disableDeprecatedAbiDialog) { 246 return; 247 } 248 final String appPrimaryAbi = r.info.applicationInfo.primaryCpuAbi; 249 final String appSecondaryAbi = r.info.applicationInfo.secondaryCpuAbi; 250 final boolean appContainsOnly32bitLibraries = 251 (appPrimaryAbi != null && appSecondaryAbi == null && !appPrimaryAbi.contains("64")); 252 final boolean is64BitDevice = 253 ArrayUtils.find(Build.SUPPORTED_ABIS, abi -> abi.contains("64")) != null; 254 if (is64BitDevice && appContainsOnly32bitLibraries) { 255 mUiHandler.showDeprecatedAbiDialog(r); 256 } 257 } 258 showPageSizeMismatchDialogIfNeeded(ActivityRecord r)259 public void showPageSizeMismatchDialogIfNeeded(ActivityRecord r) { 260 // Don't show dialog if the app compat is enabled using property 261 final boolean appCompatEnabled = SystemProperties.getBoolean( 262 "bionic.linker.16kb.app_compat.enabled", false); 263 if (appCompatEnabled) { 264 return; 265 } 266 boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384; 267 if (is16KbDevice) { 268 mUiHandler.showPageSizeMismatchDialog(r); 269 } 270 } 271 272 /** 273 * Called when an activity is being started. 274 * 275 * @param r record for the activity being started 276 */ onStartActivity(ActivityRecord r)277 public void onStartActivity(ActivityRecord r) { 278 showUnsupportedCompileSdkDialogIfNeeded(r); 279 showUnsupportedDisplaySizeDialogIfNeeded(r); 280 showDeprecatedTargetDialogIfNeeded(r); 281 showDeprecatedAbiDialogIfNeeded(r); 282 if (Flags.appCompatOption16kb()) { 283 showPageSizeMismatchDialogIfNeeded(r); 284 } 285 } 286 287 /** 288 * Called when an activity was previously started and is being resumed. 289 * 290 * @param r record for the activity being resumed 291 */ onResumeActivity(ActivityRecord r)292 public void onResumeActivity(ActivityRecord r) { 293 showUnsupportedDisplaySizeDialogIfNeeded(r); 294 } 295 296 /** 297 * Called by ActivityManagerService when package data has been cleared. 298 * 299 * @param name the package whose data has been cleared 300 * @param userId the user where the package resides. 301 */ onPackageDataCleared(String name, int userId)302 public void onPackageDataCleared(String name, int userId) { 303 removePackageAndHideDialogs(name, userId); 304 } 305 306 /** 307 * Called by ActivityManagerService when a package has been uninstalled. 308 * 309 * @param name the package that has been uninstalled 310 * @param userId the user where the package resides. 311 */ onPackageUninstalled(String name, int userId)312 public void onPackageUninstalled(String name, int userId) { 313 removePackageAndHideDialogs(name, userId); 314 } 315 316 /** 317 * Called by ActivityManagerService when the default display density has changed. 318 */ onDensityChanged()319 public void onDensityChanged() { 320 mUiHandler.hideUnsupportedDisplaySizeDialog(); 321 } 322 323 /** 324 * Does what it says on the tin. 325 */ removePackageAndHideDialogs(String name, int userId)326 private void removePackageAndHideDialogs(String name, int userId) { 327 // Per-user AppWarnings only affects the behavior of the devices that enable the visible 328 // background users. 329 // To preserve existing behavior of the other devices, handle AppWarnings as a system user 330 // regardless of the actual user. 331 if (!isVisibleBackgroundUsersEnabled()) { 332 userId = USER_SYSTEM; 333 } else { 334 // If the userId is of a profile, use the parent user ID, 335 // since the warning dialogs and the flags for a package are handled per profile group. 336 userId = mUserManagerInternal.getProfileParentId(userId); 337 } 338 339 mUiHandler.hideDialogsForPackage(name, userId); 340 341 synchronized (mPackageFlags) { 342 final Pair<Integer, String> packageKey = Pair.create(userId, name); 343 if (mPackageFlags.remove(packageKey) != null) { 344 mWriteConfigTask.schedule(); 345 } 346 } 347 } 348 349 /** 350 * Hides the "unsupported display size" warning. 351 * <p> 352 * <strong>Note:</strong> Must be called on the UI thread. 353 */ 354 @UiThread hideUnsupportedDisplaySizeDialogUiThread()355 private void hideUnsupportedDisplaySizeDialogUiThread() { 356 if (mUnsupportedDisplaySizeDialogs == null) { 357 return; 358 } 359 360 for (int i = 0; i < mUnsupportedDisplaySizeDialogs.size(); i++) { 361 mUnsupportedDisplaySizeDialogs.valueAt(i).dismiss(); 362 } 363 mUnsupportedDisplaySizeDialogs.clear(); 364 } 365 366 /** 367 * Shows the "unsupported display size" warning for the given application. 368 * <p> 369 * <strong>Note:</strong> Must be called on the UI thread. 370 * 371 * @param ar record for the activity that triggered the warning 372 */ 373 @UiThread showUnsupportedDisplaySizeDialogUiThread(@onNull ActivityRecord ar)374 private void showUnsupportedDisplaySizeDialogUiThread(@NonNull ActivityRecord ar) { 375 final int userId = getUserIdForActivity(ar); 376 UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog; 377 if (mUnsupportedDisplaySizeDialogs != null) { 378 unsupportedDisplaySizeDialog = mUnsupportedDisplaySizeDialogs.get(userId); 379 if (unsupportedDisplaySizeDialog != null) { 380 unsupportedDisplaySizeDialog.dismiss(); 381 mUnsupportedDisplaySizeDialogs.remove(userId); 382 } 383 } 384 if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) { 385 unsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( 386 AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); 387 unsupportedDisplaySizeDialog.show(); 388 if (mUnsupportedDisplaySizeDialogs == null) { 389 mUnsupportedDisplaySizeDialogs = new SparseArray<>(); 390 } 391 mUnsupportedDisplaySizeDialogs.put(userId, unsupportedDisplaySizeDialog); 392 } 393 } 394 395 /** 396 * Shows the "unsupported compile SDK" warning for the given application. 397 * <p> 398 * <strong>Note:</strong> Must be called on the UI thread. 399 * 400 * @param ar record for the activity that triggered the warning 401 */ 402 @UiThread showUnsupportedCompileSdkDialogUiThread(@onNull ActivityRecord ar)403 private void showUnsupportedCompileSdkDialogUiThread(@NonNull ActivityRecord ar) { 404 final int userId = getUserIdForActivity(ar); 405 UnsupportedCompileSdkDialog unsupportedCompileSdkDialog; 406 if (mUnsupportedCompileSdkDialogs != null) { 407 unsupportedCompileSdkDialog = mUnsupportedCompileSdkDialogs.get(userId); 408 if (unsupportedCompileSdkDialog != null) { 409 unsupportedCompileSdkDialog.dismiss(); 410 mUnsupportedCompileSdkDialogs.remove(userId); 411 } 412 } 413 if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_COMPILE_SDK)) { 414 unsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog( 415 AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); 416 unsupportedCompileSdkDialog.show(); 417 if (mUnsupportedCompileSdkDialogs == null) { 418 mUnsupportedCompileSdkDialogs = new SparseArray<>(); 419 } 420 mUnsupportedCompileSdkDialogs.put(userId, unsupportedCompileSdkDialog); 421 } 422 } 423 424 /** 425 * Shows the "deprecated target sdk version" warning for the given application. 426 * <p> 427 * <strong>Note:</strong> Must be called on the UI thread. 428 * 429 * @param ar record for the activity that triggered the warning 430 */ 431 @UiThread showDeprecatedTargetSdkDialogUiThread(@onNull ActivityRecord ar)432 private void showDeprecatedTargetSdkDialogUiThread(@NonNull ActivityRecord ar) { 433 final int userId = getUserIdForActivity(ar); 434 DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog; 435 if (mDeprecatedTargetSdkVersionDialogs != null) { 436 deprecatedTargetSdkVersionDialog = mDeprecatedTargetSdkVersionDialogs.get(userId); 437 if (deprecatedTargetSdkVersionDialog != null) { 438 deprecatedTargetSdkVersionDialog.dismiss(); 439 mDeprecatedTargetSdkVersionDialogs.remove(userId); 440 } 441 } 442 if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) { 443 deprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog( 444 AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); 445 deprecatedTargetSdkVersionDialog.show(); 446 if (mDeprecatedTargetSdkVersionDialogs == null) { 447 mDeprecatedTargetSdkVersionDialogs = new SparseArray<>(); 448 } 449 mDeprecatedTargetSdkVersionDialogs.put(userId, deprecatedTargetSdkVersionDialog); 450 } 451 } 452 453 /** 454 * Shows the "deprecated abi" warning for the given application. 455 * <p> 456 * <strong>Note:</strong> Must be called on the UI thread. 457 * 458 * @param ar record for the activity that triggered the warning 459 */ 460 @UiThread showDeprecatedAbiDialogUiThread(@onNull ActivityRecord ar)461 private void showDeprecatedAbiDialogUiThread(@NonNull ActivityRecord ar) { 462 final int userId = getUserIdForActivity(ar); 463 DeprecatedAbiDialog deprecatedAbiDialog; 464 if (mDeprecatedAbiDialogs != null) { 465 deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId); 466 if (deprecatedAbiDialog != null) { 467 deprecatedAbiDialog.dismiss(); 468 mDeprecatedAbiDialogs.remove(userId); 469 } 470 } 471 if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) { 472 deprecatedAbiDialog = new DeprecatedAbiDialog( 473 AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); 474 deprecatedAbiDialog.show(); 475 if (mDeprecatedAbiDialogs == null) { 476 mDeprecatedAbiDialogs = new SparseArray<>(); 477 } 478 mDeprecatedAbiDialogs.put(userId, deprecatedAbiDialog); 479 } 480 } 481 482 @UiThread showPageSizeMismatchDialogUiThread(@onNull ActivityRecord ar)483 private void showPageSizeMismatchDialogUiThread(@NonNull ActivityRecord ar) { 484 String warning = 485 mAtm.mContext 486 .getPackageManager() 487 .getPageSizeCompatWarningMessage(ar.info.packageName); 488 if (warning == null) { 489 return; 490 } 491 492 final int userId = getUserIdForActivity(ar); 493 PageSizeMismatchDialog pageSizeMismatchDialog; 494 if (mPageSizeMismatchDialogs != null) { 495 pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId); 496 if (pageSizeMismatchDialog != null) { 497 pageSizeMismatchDialog.dismiss(); 498 mPageSizeMismatchDialogs.remove(userId); 499 } 500 } 501 if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) { 502 Context context = getUiContextForActivity(ar); 503 // PageSizeMismatchDialog has link in message which should open in browser. 504 // Starting activity from non-activity context is not allowed and flag 505 // FLAG_ACTIVITY_NEW_TASK is needed to start activity. 506 context = new ContextThemeWrapper(context, context.getThemeResId()) { 507 @Override 508 public void startActivity(Intent intent) { 509 // PageSizeMismatch dialog stays on top of the browser even after opening link 510 // set broadcast to close the dialog when link has been clicked. 511 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 512 513 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 514 super.startActivity(intent); 515 } 516 }; 517 pageSizeMismatchDialog = 518 new PageSizeMismatchDialog( 519 AppWarnings.this, 520 context, 521 ar.info.applicationInfo, 522 userId, 523 warning); 524 pageSizeMismatchDialog.show(); 525 if (mPageSizeMismatchDialogs == null) { 526 mPageSizeMismatchDialogs = new SparseArray<>(); 527 } 528 mPageSizeMismatchDialogs.put(userId, pageSizeMismatchDialog); 529 } 530 } 531 532 /** 533 * Dismisses all warnings for the given package. 534 * <p> 535 * <strong>Note:</strong> Must be called on the UI thread. 536 * 537 * @param name the package for which warnings should be dismissed, or {@code null} to dismiss 538 * all warnings 539 * @param userId the user where the package resides. 540 */ 541 @UiThread hideDialogsForPackageUiThread(String name, int userId)542 private void hideDialogsForPackageUiThread(String name, int userId) { 543 // Hides the "unsupported display" dialog if necessary. 544 if (mUnsupportedDisplaySizeDialogs != null) { 545 UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog = 546 mUnsupportedDisplaySizeDialogs.get(userId); 547 if (unsupportedDisplaySizeDialog != null && (name == null || name.equals( 548 unsupportedDisplaySizeDialog.mPackageName))) { 549 unsupportedDisplaySizeDialog.dismiss(); 550 mUnsupportedDisplaySizeDialogs.remove(userId); 551 } 552 } 553 554 // Hides the "unsupported compile SDK" dialog if necessary. 555 if (mUnsupportedCompileSdkDialogs != null) { 556 UnsupportedCompileSdkDialog unsupportedCompileSdkDialog = 557 mUnsupportedCompileSdkDialogs.get(userId); 558 if (unsupportedCompileSdkDialog != null && (name == null || name.equals( 559 unsupportedCompileSdkDialog.mPackageName))) { 560 unsupportedCompileSdkDialog.dismiss(); 561 mUnsupportedCompileSdkDialogs.remove(userId); 562 } 563 } 564 565 // Hides the "deprecated target sdk version" dialog if necessary. 566 if (mDeprecatedTargetSdkVersionDialogs != null) { 567 DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog = 568 mDeprecatedTargetSdkVersionDialogs.get(userId); 569 if (deprecatedTargetSdkVersionDialog != null && (name == null || name.equals( 570 deprecatedTargetSdkVersionDialog.mPackageName))) { 571 deprecatedTargetSdkVersionDialog.dismiss(); 572 mDeprecatedTargetSdkVersionDialogs.remove(userId); 573 } 574 } 575 576 // Hides the "deprecated abi" dialog if necessary. 577 if (mDeprecatedAbiDialogs != null) { 578 DeprecatedAbiDialog deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId); 579 if (deprecatedAbiDialog != null && (name == null || name.equals( 580 deprecatedAbiDialog.mPackageName))) { 581 deprecatedAbiDialog.dismiss(); 582 mDeprecatedAbiDialogs.remove(userId); 583 } 584 } 585 586 // Hides the "page size app compat" dialog if necessary. 587 if (mPageSizeMismatchDialogs != null) { 588 PageSizeMismatchDialog pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId); 589 if (pageSizeMismatchDialog != null 590 && (name == null || name.equals(pageSizeMismatchDialog.mPackageName))) { 591 pageSizeMismatchDialog.dismiss(); 592 mPageSizeMismatchDialogs.remove(userId); 593 } 594 } 595 } 596 597 /** 598 * Returns the value of the flag for the given package. 599 * 600 * @param userId the user where the package resides. 601 * @param name the package from which to retrieve the flag 602 * @param flag the bitmask for the flag to retrieve 603 * @return {@code true} if the flag is enabled, {@code false} otherwise 604 */ hasPackageFlag(int userId, String name, int flag)605 boolean hasPackageFlag(int userId, String name, int flag) { 606 return (getPackageFlags(userId, name) & flag) == flag; 607 } 608 609 /** 610 * Sets the flag for the given package to the specified value. 611 * 612 * @param userId the user where the package resides. 613 * @param name the package on which to set the flag 614 * @param flag the bitmask for flag to set 615 * @param enabled the value to set for the flag 616 */ setPackageFlag(int userId, String name, int flag, boolean enabled)617 void setPackageFlag(int userId, String name, int flag, boolean enabled) { 618 synchronized (mPackageFlags) { 619 final int curFlags = getPackageFlags(userId, name); 620 final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag); 621 if (curFlags != newFlags) { 622 final Pair<Integer, String> packageKey = Pair.create(userId, name); 623 if (newFlags != 0) { 624 mPackageFlags.put(packageKey, newFlags); 625 } else { 626 mPackageFlags.remove(packageKey); 627 } 628 mWriteConfigTask.schedule(); 629 } 630 } 631 } 632 633 /** 634 * Returns the bitmask of flags set for the specified package. 635 */ getPackageFlags(int userId, String packageName)636 private int getPackageFlags(int userId, String packageName) { 637 synchronized (mPackageFlags) { 638 final Pair<Integer, String> packageKey = Pair.create(userId, packageName); 639 return mPackageFlags.getOrDefault(packageKey, 0); 640 } 641 } 642 643 /** 644 * Clear all the package flags for given user. 645 */ clearAllPackageFlagsForUser(int userId)646 private void clearAllPackageFlagsForUser(int userId) { 647 synchronized (mPackageFlags) { 648 boolean hasPackageFlagsForUser = false; 649 for (int i = mPackageFlags.size() - 1; i >= 0; i--) { 650 Pair<Integer, String> key = mPackageFlags.keyAt(i); 651 if (key.first == userId) { 652 hasPackageFlagsForUser = true; 653 mPackageFlags.remove(key); 654 } 655 } 656 657 if (hasPackageFlagsForUser) { 658 mWriteConfigTask.schedule(); 659 } 660 } 661 } 662 663 /** 664 * Returns the user ID for handling AppWarnings per user. 665 * Per-user AppWarnings only affects the behavior of the devices that enable 666 * the visible background users. 667 * If the device doesn't enable visible background users, it will return the system user ID 668 * for handling AppWarnings as a system user regardless of the actual user 669 * to preserve existing behavior of the device. 670 * Otherwise, it will return the main user (i.e., not a profile) that is assigned to the display 671 * where the activity is launched. 672 */ getUserIdForActivity(@onNull ActivityRecord ar)673 private @UserIdInt int getUserIdForActivity(@NonNull ActivityRecord ar) { 674 if (!isVisibleBackgroundUsersEnabled()) { 675 return USER_SYSTEM; 676 } 677 678 if (ar.mUserId == USER_SYSTEM) { 679 return getUserAssignedToDisplay(ar.mDisplayContent.getDisplayId()); 680 } 681 682 return mUserManagerInternal.getProfileParentId(ar.mUserId); 683 } 684 685 /** 686 * Returns the UI context for handling AppWarnings per user. 687 * Per-user AppWarnings only affects the behavior of the devices that enable 688 * the visible background users. 689 * If the device enables the visible background users, it will return the UI context associated 690 * with the assigned user and the display where the activity is launched. 691 * If the HSUM device doesn't enable the visible background users, it will return the UI context 692 * associated with the current user and the default display. 693 * Otherwise, it will return the UI context associated with the system user and the default 694 * display. 695 */ getUiContextForActivity(@onNull ActivityRecord ar)696 private Context getUiContextForActivity(@NonNull ActivityRecord ar) { 697 if (!isVisibleBackgroundUsersEnabled()) { 698 if (!isHeadlessSystemUserMode()) { 699 return mAtm.getUiContext(); 700 } 701 702 Context uiContextForCurrentUser = mAtm.getUiContext().createContextAsUser( 703 new UserHandle(mAtm.getCurrentUserId()), /* flags= */ 0); 704 return uiContextForCurrentUser; 705 } 706 707 DisplayContent dc = ar.mDisplayContent; 708 Context systemUiContext = dc.getDisplayPolicy().getSystemUiContext(); 709 int assignedUser = getUserAssignedToDisplay(dc.getDisplayId()); 710 Context uiContextForUser = systemUiContext.createContextAsUser( 711 new UserHandle(assignedUser), /* flags= */ 0); 712 return uiContextForUser; 713 } 714 715 /** 716 * Returns the main user that is assigned to the display. 717 * 718 * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}. 719 */ getUserAssignedToDisplay(int displayId)720 private @UserIdInt int getUserAssignedToDisplay(int displayId) { 721 return mUserManagerInternal.getUserAssignedToDisplay(displayId); 722 } 723 724 /** 725 * Handles messages on the system process UI thread. 726 */ 727 private final class UiHandler extends Handler { 728 private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1; 729 private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2; 730 private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3; 731 private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4; 732 private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5; 733 private static final int MSG_SHOW_DEPRECATED_ABI_DIALOG = 6; 734 private static final int MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG = 7; 735 UiHandler(Looper looper)736 public UiHandler(Looper looper) { 737 super(looper, null, true); 738 } 739 740 @Override handleMessage(Message msg)741 public void handleMessage(Message msg) { 742 switch (msg.what) { 743 case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { 744 final ActivityRecord ar = (ActivityRecord) msg.obj; 745 showUnsupportedDisplaySizeDialogUiThread(ar); 746 } break; 747 case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { 748 hideUnsupportedDisplaySizeDialogUiThread(); 749 } break; 750 case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: { 751 final ActivityRecord ar = (ActivityRecord) msg.obj; 752 showUnsupportedCompileSdkDialogUiThread(ar); 753 } break; 754 case MSG_HIDE_DIALOGS_FOR_PACKAGE: { 755 final String name = (String) msg.obj; 756 final int userId = (int) msg.arg1; 757 hideDialogsForPackageUiThread(name, userId); 758 } break; 759 case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: { 760 final ActivityRecord ar = (ActivityRecord) msg.obj; 761 showDeprecatedTargetSdkDialogUiThread(ar); 762 } break; 763 case MSG_SHOW_DEPRECATED_ABI_DIALOG: { 764 final ActivityRecord ar = (ActivityRecord) msg.obj; 765 showDeprecatedAbiDialogUiThread(ar); 766 } break; 767 case MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG: { 768 final ActivityRecord ar = (ActivityRecord) msg.obj; 769 showPageSizeMismatchDialogUiThread(ar); 770 } break; 771 } 772 } 773 showUnsupportedDisplaySizeDialog(ActivityRecord r)774 public void showUnsupportedDisplaySizeDialog(ActivityRecord r) { 775 removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG); 776 obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget(); 777 } 778 hideUnsupportedDisplaySizeDialog()779 public void hideUnsupportedDisplaySizeDialog() { 780 removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); 781 sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); 782 } 783 showUnsupportedCompileSdkDialog(ActivityRecord r)784 public void showUnsupportedCompileSdkDialog(ActivityRecord r) { 785 removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG); 786 obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget(); 787 } 788 showDeprecatedTargetDialog(ActivityRecord r)789 public void showDeprecatedTargetDialog(ActivityRecord r) { 790 removeMessages(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG); 791 obtainMessage(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG, r).sendToTarget(); 792 } 793 showDeprecatedAbiDialog(ActivityRecord r)794 public void showDeprecatedAbiDialog(ActivityRecord r) { 795 removeMessages(MSG_SHOW_DEPRECATED_ABI_DIALOG); 796 obtainMessage(MSG_SHOW_DEPRECATED_ABI_DIALOG, r).sendToTarget(); 797 } 798 hideDialogsForPackage(String name, int userId)799 public void hideDialogsForPackage(String name, int userId) { 800 obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget(); 801 } 802 showPageSizeMismatchDialog(ActivityRecord r)803 public void showPageSizeMismatchDialog(ActivityRecord r) { 804 removeMessages(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG); 805 obtainMessage(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG, r).sendToTarget(); 806 } 807 } 808 809 static class BaseDialog { 810 final AppWarnings mManager; 811 final Context mUiContext; 812 final String mPackageName; 813 final int mUserId; 814 AlertDialog mDialog; 815 private BroadcastReceiver mCloseReceiver; 816 BaseDialog(AppWarnings manager, Context uiContext, String packageName, int userId)817 BaseDialog(AppWarnings manager, Context uiContext, String packageName, int userId) { 818 mManager = manager; 819 mUiContext = uiContext; 820 mPackageName = packageName; 821 mUserId = userId; 822 } 823 824 @UiThread show()825 void show() { 826 if (mDialog == null) return; 827 if (mCloseReceiver == null) { 828 mCloseReceiver = new BroadcastReceiver() { 829 @Override 830 public void onReceive(Context context, Intent intent) { 831 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { 832 mManager.mUiHandler.hideDialogsForPackage(mPackageName, mUserId); 833 } 834 } 835 }; 836 mUiContext.registerReceiver(mCloseReceiver, 837 new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), 838 Context.RECEIVER_EXPORTED); 839 } 840 Slog.w(TAG, "Showing " + getClass().getSimpleName() + " for package " + mPackageName); 841 mDialog.show(); 842 } 843 844 @UiThread dismiss()845 void dismiss() { 846 if (mDialog == null) return; 847 if (mCloseReceiver != null) { 848 mUiContext.unregisterReceiver(mCloseReceiver); 849 mCloseReceiver = null; 850 } 851 mDialog.dismiss(); 852 mDialog = null; 853 } 854 } 855 856 private final class WriteConfigTask implements Runnable { 857 private static final long WRITE_CONFIG_DELAY_MS = 10000; 858 final AtomicReference<ArrayMap<Pair<Integer, String>, Integer>> mPendingPackageFlags = 859 new AtomicReference<>(); 860 861 @Override run()862 public void run() { 863 final ArrayMap<Pair<Integer, String>, Integer> packageFlags = 864 mPendingPackageFlags.getAndSet(null); 865 if (packageFlags != null) { 866 writeConfigToFile(packageFlags); 867 } 868 } 869 870 @GuardedBy("mPackageFlags") schedule()871 void schedule() { 872 if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) { 873 IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS); 874 } 875 } 876 } 877 878 /** Writes the configuration file. */ 879 @WorkerThread writeConfigToFile(@onNull ArrayMap<Pair<Integer, String>, Integer> packageFlags)880 private void writeConfigToFile(@NonNull ArrayMap<Pair<Integer, String>, Integer> packageFlags) { 881 FileOutputStream fos = null; 882 try { 883 fos = mConfigFile.startWrite(); 884 885 final TypedXmlSerializer out = Xml.resolveSerializer(fos); 886 out.startDocument(null, true); 887 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 888 out.startTag(null, "packages"); 889 890 for (int i = 0; i < packageFlags.size(); i++) { 891 final Pair<Integer, String> key = packageFlags.keyAt(i); 892 final int userId = key.first; 893 final String packageName = key.second; 894 final int mode = packageFlags.valueAt(i); 895 if (mode == 0) { 896 continue; 897 } 898 out.startTag(null, "package"); 899 out.attributeInt(null, "user", userId); 900 out.attribute(null, "name", packageName); 901 out.attributeInt(null, "flags", mode); 902 out.endTag(null, "package"); 903 } 904 905 out.endTag(null, "packages"); 906 out.endDocument(); 907 908 mConfigFile.finishWrite(fos); 909 } catch (java.io.IOException e1) { 910 Slog.w(TAG, "Error writing package metadata", e1); 911 if (fos != null) { 912 mConfigFile.failWrite(fos); 913 } 914 } 915 } 916 917 /** 918 * Reads the configuration file and populates the package flags. 919 * <p> 920 * <strong>Note:</strong> Must be called from #onSystemReady() (and thus on the 921 * ActivityManagerService thread) since we don't synchronize on config. 922 */ readConfigFromFileAmsThread()923 private void readConfigFromFileAmsThread() { 924 FileInputStream fis = null; 925 926 try { 927 fis = mConfigFile.openRead(); 928 929 final TypedXmlPullParser parser = Xml.resolvePullParser(fis); 930 931 int eventType = parser.getEventType(); 932 while (eventType != XmlPullParser.START_TAG && 933 eventType != XmlPullParser.END_DOCUMENT) { 934 eventType = parser.next(); 935 } 936 if (eventType == XmlPullParser.END_DOCUMENT) { 937 return; 938 } 939 940 String tagName = parser.getName(); 941 if ("packages".equals(tagName)) { 942 eventType = parser.next(); 943 boolean writeConfigToFileNeeded = false; 944 do { 945 if (eventType == XmlPullParser.START_TAG) { 946 tagName = parser.getName(); 947 if (parser.getDepth() == 2) { 948 if ("package".equals(tagName)) { 949 final int userId = parser.getAttributeInt( 950 null, "user", USER_NULL); 951 final String name = parser.getAttributeValue(null, "name"); 952 if (name != null) { 953 int flagsInt = parser.getAttributeInt(null, "flags", 0); 954 if (userId != USER_NULL) { 955 final Pair<Integer, String> packageKey = 956 Pair.create(userId, name); 957 mPackageFlags.put(packageKey, flagsInt); 958 } else { 959 // This is for compatibility with existing configuration 960 // file written from legacy logic(pre-V) which does not have 961 // the flags per-user. (b/296334639) 962 writeConfigToFileNeeded = true; 963 if (!isVisibleBackgroundUsersEnabled()) { 964 // To preserve existing behavior of the devices that 965 // doesn't enable visible background users, populate 966 // the flags for a package as the system user. 967 final Pair<Integer, String> packageKey = 968 Pair.create(USER_SYSTEM, name); 969 mPackageFlags.put(packageKey, flagsInt); 970 } else { 971 // To manage the flags per user in the device that 972 // enable visible background users, populate the flags 973 // for all existing non-profile human user. 974 UserInfo[] users = mUserManagerInternal.getUserInfos(); 975 for (UserInfo userInfo : users) { 976 if (!userInfo.isFull()) { 977 continue; 978 } 979 final Pair<Integer, String> packageKey = 980 Pair.create(userInfo.id, name); 981 mPackageFlags.put(packageKey, flagsInt); 982 } 983 } 984 } 985 } 986 } 987 } 988 } 989 eventType = parser.next(); 990 } while (eventType != XmlPullParser.END_DOCUMENT); 991 992 if (writeConfigToFileNeeded) { 993 mWriteConfigTask.schedule(); 994 } 995 } 996 } catch (XmlPullParserException e) { 997 Slog.w(TAG, "Error reading package metadata", e); 998 } catch (java.io.IOException e) { 999 if (fis != null) Slog.w(TAG, "Error reading package metadata", e); 1000 } finally { 1001 if (fis != null) { 1002 try { 1003 fis.close(); 1004 } catch (java.io.IOException e1) { 1005 } 1006 } 1007 } 1008 } 1009 } 1010