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