diff options
106 files changed, 2429 insertions, 600 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index ce3e985e22d5..b54022bf47f2 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -64,6 +64,7 @@ aconfig_srcjars = [ ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", ":android.tracing.flags-aconfig-java{.generated_srcjars}", ":android.appwidget.flags-aconfig-java{.generated_srcjars}", + ":android.webkit.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -762,3 +763,19 @@ java_aconfig_library { aconfig_declarations: "android.appwidget.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// WebView +aconfig_declarations { + name: "android.webkit.flags-aconfig", + package: "android.webkit", + srcs: [ + "core/java/android/webkit/*.aconfig", + "services/core/java/com/android/server/webkit/*.aconfig", + ], +} + +java_aconfig_library { + name: "android.webkit.flags-aconfig-java", + aconfig_declarations: "android.webkit.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Ravenwood.bp b/Ravenwood.bp index 03a23ba15273..ca73378c8e81 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -32,7 +32,6 @@ java_genrule { cmd: "$(location hoststubgen) " + "@$(location ravenwood/ravenwood-standard-options.txt) " + - "--out-stub-jar $(location ravenwood_stub.jar) " + "--out-impl-jar $(location ravenwood.jar) " + "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " + @@ -49,7 +48,6 @@ java_genrule { ], out: [ "ravenwood.jar", - "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional. // Following files are created just as FYI. "hoststubgen_keep_all.txt", diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index c42c7ca25133..2ace6028456e 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -128,6 +128,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration(); final InsetsState mOutInsetsState = new InsetsState(); final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array(); + final Bundle mOutBundle = new Bundle(); final IWindow mWindow; final View mView; final WindowManager.LayoutParams mParams; @@ -136,7 +137,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase final SurfaceControl mOutSurfaceControl; final IntSupplier mViewVisibility; - + int mRelayoutSeq; int mFlags; RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) { @@ -152,10 +153,11 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase void runBenchmark(BenchmarkState state) throws RemoteException { final IWindowSession session = WindowManagerGlobal.getWindowSession(); while (state.keepRunning()) { + mRelayoutSeq++; session.relayout(mWindow, mParams, mWidth, mHeight, - mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */, + mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */, mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, - mOutControls, new Bundle()); + mOutControls, mOutBundle); } } } diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt index 948e64f22f20..9ffb70496c59 100644 --- a/api/coverage/tools/ExtractFlaggedApis.kt +++ b/api/coverage/tools/ExtractFlaggedApis.kt @@ -25,7 +25,7 @@ fun main(args: Array<String>) { var cb = ApiFile.parseApi(listOf(File(args[0]))) val flagToApi = mutableMapOf<String, MutableList<String>>() cb.getPackages() - .allTopLevelClasses() + .allClasses() .filter { it.methods().size > 0 } .forEach { for (method in it.methods()) { diff --git a/core/api/current.txt b/core/api/current.txt index be76c8ac6ac2..812d4cd67ab7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -36760,6 +36760,7 @@ package android.provider { field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS"; field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS"; field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS"; + field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS"; field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS"; field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS"; field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL"; @@ -36847,6 +36848,7 @@ package android.provider { field public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled"; field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE"; field public static final String EXTRA_AUTHORITIES = "authorities"; + field @FlaggedApi("android.app.modes_api") public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID"; field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED"; field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST"; @@ -54185,7 +54187,7 @@ package android.view.accessibility { method public boolean isEnabled(); method public boolean isFocusable(); method public boolean isFocused(); - method public boolean isGranularScrollingSupported(); + method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported(); method public boolean isHeading(); method public boolean isImportantForAccessibility(); method public boolean isLongClickable(); @@ -54235,7 +54237,7 @@ package android.view.accessibility { method public void setError(CharSequence); method public void setFocusable(boolean); method public void setFocused(boolean); - method public void setGranularScrollingSupported(boolean); + method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean); method public void setHeading(boolean); method public void setHintText(CharSequence); method public void setImportantForAccessibility(boolean); @@ -54290,7 +54292,7 @@ package android.view.accessibility { field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT"; field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE"; field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT"; - field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT"; + field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT"; field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT"; field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE"; @@ -54393,10 +54395,9 @@ package android.view.accessibility { public static final class AccessibilityNodeInfo.CollectionInfo { ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean); ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int); - ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int, int, int); method public int getColumnCount(); - method public int getImportantForAccessibilityItemCount(); - method public int getItemCount(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getImportantForAccessibilityItemCount(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getItemCount(); method public int getRowCount(); method public int getSelectionMode(); method public boolean isHierarchical(); @@ -54405,18 +54406,18 @@ package android.view.accessibility { field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2 field public static final int SELECTION_MODE_NONE = 0; // 0x0 field public static final int SELECTION_MODE_SINGLE = 1; // 0x1 - field public static final int UNDEFINED = -1; // 0xffffffff - } - - public static final class AccessibilityNodeInfo.CollectionInfo.Builder { - ctor public AccessibilityNodeInfo.CollectionInfo.Builder(); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build(); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int); + field @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final int UNDEFINED = -1; // 0xffffffff + } + + @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final class AccessibilityNodeInfo.CollectionInfo.Builder { + ctor @FlaggedApi("android.view.accessibility.collection_info_item_counts") public AccessibilityNodeInfo.CollectionInfo.Builder(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int); } public static final class AccessibilityNodeInfo.CollectionItemInfo { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 32d252ebda29..275fe7790e84 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3868,8 +3868,9 @@ package android.content.pm { public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL"; @@ -3883,12 +3884,20 @@ package android.content.pm { field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID"; field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; field public static final int LOCATION_DATA_APP = 0; // 0x0 field public static final int LOCATION_MEDIA_DATA = 2; // 0x2 field public static final int LOCATION_MEDIA_OBB = 1; // 0x1 field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0 field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1 field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0 } public static class PackageInstaller.InstallInfo { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index bb335fae887b..b5f7f232660b 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.DETECT_SCREEN_CAPTURE; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; @@ -9439,6 +9440,15 @@ public class Activity extends ContextThemeWrapper ActivityClient.getInstance().enableTaskLocaleOverride(mToken); } + /** + * Request ActivityRecordInputSink to enable or disable blocking input events. + * @hide + */ + @RequiresPermission(INTERNAL_SYSTEM_WINDOW) + public void setActivityRecordInputSinkEnabled(boolean enabled) { + ActivityClient.getInstance().setActivityRecordInputSinkEnabled(mToken, enabled); + } + class HostCallbacks extends FragmentHostCallback<Activity> { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index b35e87b541d3..b8bd030872c1 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -16,6 +16,8 @@ package android.app; +import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; + import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.content.ComponentName; @@ -614,6 +616,15 @@ public class ActivityClient { } } + @RequiresPermission(INTERNAL_SYSTEM_WINDOW) + void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) { + try { + getActivityClientController().setActivityRecordInputSinkEnabled(activityToken, enabled); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** * Shows or hides a Camera app compat toggle for stretched issues with the requested state. * diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index a3c5e1c67e1b..7370fc36c23e 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -191,4 +191,14 @@ interface IActivityClientController { */ boolean isRequestedToLaunchInTaskFragment(in IBinder activityToken, in IBinder taskFragmentToken); + + /** + * Enable or disable ActivityRecordInputSink to block input events. + * + * @param token The token for the activity that requests to toggle. + * @param enabled Whether the input evens are blocked by ActivityRecordInputSink. + */ + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.INTERNAL_SYSTEM_WINDOW)") + oneway void setActivityRecordInputSinkEnabled(in IBinder activityToken, boolean enabled); } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 56d0d1f2843d..6c9c14fbc83a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -51,6 +51,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.UserHandle; +import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Adjustment; import android.service.notification.Condition; @@ -1253,7 +1254,8 @@ public class NotificationManager { * <p> * If this method returns true, calls to * {@link #updateAutomaticZenRule(String, AutomaticZenRule)} may fail and apps should defer - * rule management to system settings/uis. + * rule management to system settings/uis via + * {@link Settings#ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}. */ @FlaggedApi(Flags.FLAG_MODES_API) public boolean areAutomaticZenRulesUserManaged() { diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index cc56a1cee7a2..d8448dcb4f9c 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -72,6 +72,7 @@ per-file *Notification* = file:/packages/SystemUI/OWNERS per-file *Zen* = file:/packages/SystemUI/OWNERS per-file *StatusBar* = file:/packages/SystemUI/OWNERS per-file *UiModeManager* = file:/packages/SystemUI/OWNERS +per-file notification.aconfig = file:/packages/SystemUI/OWNERS # PackageManager per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index bbd07b8c21ea..35ce10223aa6 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -48,3 +48,10 @@ flag { description: "Update DumpSys to include information about migrated APIs in DPE" bug: "304999634" } + +flag { + name: "quiet_mode_credential_bug_fix" + namespace: "enterprise" + description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped." + bug: "293441361" +} diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING index 49a4467b1f67..47a152aa6cca 100644 --- a/core/java/android/app/time/TEST_MAPPING +++ b/core/java/android/app/time/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.app." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "FrameworksServicesTests", "options": [ diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING index c050a55a3e18..9517fb99b04a 100644 --- a/core/java/android/app/timedetector/TEST_MAPPING +++ b/core/java/android/app/timedetector/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.app." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "FrameworksServicesTests", "options": [ diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING index 46656d125b70..fd41b869efaf 100644 --- a/core/java/android/app/timezonedetector/TEST_MAPPING +++ b/core/java/android/app/timezonedetector/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.app." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "FrameworksServicesTests", "options": [ diff --git a/core/java/android/companion/utils/FeatureUtils.java b/core/java/android/companion/utils/FeatureUtils.java deleted file mode 100644 index a382e09ae7b2..000000000000 --- a/core/java/android/companion/utils/FeatureUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.companion.utils; - -import android.os.Binder; -import android.os.Build; -import android.provider.DeviceConfig; - -/** - * Util class for feature flags - * - * @hide - */ -public final class FeatureUtils { - - private static final String NAMESPACE_COMPANION = "companion"; - - private static final String PROPERTY_PERM_SYNC_ENABLED = "perm_sync_enabled"; - - public static boolean isPermSyncEnabled() { - // Permissions sync is always enabled in debuggable mode. - if (Build.isDebuggable()) { - return true; - } - - // Clear app identity to read the device config for feature flag. - final long identity = Binder.clearCallingIdentity(); - try { - return DeviceConfig.getBoolean(NAMESPACE_COMPANION, - PROPERTY_PERM_SYNC_ENABLED, false); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private FeatureUtils() { - } -} diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index 59ed0453bc01..1f25fd039dd8 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -16,6 +16,7 @@ package android.content.pm; +import android.app.PendingIntent; import android.content.pm.ArchivedPackageParcel; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.IPackageInstallerCallback; @@ -82,7 +83,7 @@ interface IPackageInstaller { void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") - void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle); + void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)") void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel, @@ -90,4 +91,6 @@ interface IPackageInstaller { in IntentSender statusReceiver, String installerPackageName, in UserHandle userHandle); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") + void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index e9a2aaad6579..4f0bfc7437c7 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -387,6 +387,24 @@ public class PackageInstaller { "android.content.pm.extra.UNARCHIVE_ALL_USERS"; /** + * Current status of an unarchive operation. Will be one of + * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED}, + * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY}, + * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or + * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}. + * + * <p> If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set + * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a + * failure dialog. + * + * @see #requestUnarchive + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; + + /** * A list of warnings that occurred during installation. * * @hide @@ -652,6 +670,102 @@ public class PackageInstaller { @Retention(RetentionPolicy.SOURCE) public @interface UserActionReason {} + /** + * The unarchival is possible and will commence. + * + * <p> Note that this does not mean that the unarchival has completed. This status should be + * sent before any longer asynchronous action (e.g. app download) is started. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_OK = 0; + + /** + * The user needs to interact with the installer to enable the installation. + * + * <p> An example use case for this could be that the user needs to login to allow the + * download for a paid app. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; + + /** + * Not enough storage to unarchive the application. + * + * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing + * dialog. If no action is provided, then a generic intent + * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; + + /** + * The device is not connected to the internet + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; + + /** + * The installer responsible for the unarchival is disabled. + * + * <p> Should only be used by the system. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; + + /** + * The installer responsible for the unarchival has been uninstalled + * + * <p> Should only be used by the system. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; + + /** + * Generic error: The app cannot be unarchived. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_GENERIC_ERROR = 100; + + /** + * The set of error types that can be set for + * {@link #reportUnarchivalStatus(int, int, PendingIntent)}. + * + * @hide + */ + @IntDef(value = { + UNARCHIVAL_OK, + UNARCHIVAL_ERROR_USER_ACTION_NEEDED, + UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, + UNARCHIVAL_ERROR_NO_CONNECTIVITY, + UNARCHIVAL_ERROR_INSTALLER_DISABLED, + UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED, + UNARCHIVAL_GENERIC_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UnarchivalStatus {} + + /** Default set of checksums - includes all available checksums. * @see Session#requestChecksums */ private static final int DEFAULT_CHECKSUMS = @@ -2238,8 +2352,8 @@ public class PackageInstaller { * Requests to archive a package which is currently installed. * * <p> During the archival process, the apps APKs and cache are removed from the device while - * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be - * restored again through their responsible installer. + * the user data is kept. Through the {@link #requestUnarchive} call, apps + * can be restored again through their responsible installer. * * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and * will be displayed to users with UI treatment to highlight that said apps are archived. If @@ -2278,6 +2392,10 @@ public class PackageInstaller { * <p> The installation will happen asynchronously and can be observed through * {@link android.content.Intent#ACTION_PACKAGE_ADDED}. * + * @param statusReceiver Callback used to notify whether the installer has accepted the + * unarchival request or an error has occurred. The status update will be + * sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be + * sent. * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * visible to the caller or if the package has no * installer on the device anymore to unarchive it. @@ -2290,10 +2408,10 @@ public class PackageInstaller { Manifest.permission.REQUEST_INSTALL_PACKAGES}) @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) - public void requestUnarchive(@NonNull String packageName) + public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) throws IOException, PackageManager.NameNotFoundException { try { - mInstaller.requestUnarchive(packageName, mInstallerPackageName, + mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); @@ -2303,6 +2421,39 @@ public class PackageInstaller { } } + /** + * Reports the status of an unarchival to the system. + * + * @param unarchiveId the ID provided by the system as part of the + * intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID. + * @param status is used for the system to provide the user with necessary + * follow-up steps or errors. + * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field + * should be set to specify how many additional bytes of storage + * are required to unarchive the app. + * @param userActionIntent Optional intent to start a follow up action required to + * facilitate the unarchival flow (e.g. user needs to log in). + * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists + * @hide + */ + @RequiresPermission(anyOf = { + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.REQUEST_INSTALL_PACKAGES}) + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status, + long requiredStorageBytes, @Nullable PendingIntent userActionIntent) + throws PackageManager.NameNotFoundException { + try { + mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes, + userActionIntent, new UserHandle(mUserId)); + } catch (ParcelableException e) { + e.maybeRethrow(PackageManager.NameNotFoundException.class); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // (b/239722738) This class serves as a bridge between the PackageLite class, which // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java) // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or @@ -2566,6 +2717,8 @@ public class PackageInstaller { public int developmentInstallFlags = 0; /** {@hide} */ public int unarchiveId = -1; + /** {@hide} */ + public IntentSender unarchiveIntentSender; private final ArrayMap<String, Integer> mPermissionStates; @@ -2618,6 +2771,7 @@ public class PackageInstaller { applicationEnabledSettingPersistent = source.readBoolean(); developmentInstallFlags = source.readInt(); unarchiveId = source.readInt(); + unarchiveIntentSender = source.readParcelable(null, IntentSender.class); } /** {@hide} */ @@ -2652,6 +2806,7 @@ public class PackageInstaller { ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; ret.developmentInstallFlags = developmentInstallFlags; ret.unarchiveId = unarchiveId; + ret.unarchiveIntentSender = unarchiveIntentSender; return ret; } @@ -3364,6 +3519,7 @@ public class PackageInstaller { applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); pw.printPair("unarchiveId", unarchiveId); + pw.printPair("unarchiveIntentSender", unarchiveIntentSender); pw.println(); } @@ -3408,6 +3564,7 @@ public class PackageInstaller { dest.writeBoolean(applicationEnabledSettingPersistent); dest.writeInt(developmentInstallFlags); dest.writeInt(unarchiveId); + dest.writeParcelable(unarchiveIntentSender, flags); } public static final Parcelable.Creator<SessionParams> diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index f817fb8dcaed..1100731702a2 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -82,8 +82,8 @@ public final class BinderProxy implements IBinder { private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE; private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1; /** - * Debuggable builds will throw an BinderProxyMapSizeException if the number of - * map entries exceeds: + * We will throw a BinderProxyMapSizeException if the number of map entries + * exceeds: */ private static final int CRASH_AT_SIZE = 25_000; diff --git a/core/java/android/permission/IOnPermissionsChangeListener.aidl b/core/java/android/permission/IOnPermissionsChangeListener.aidl index afacf1a74ca6..c68c0c9408a7 100644 --- a/core/java/android/permission/IOnPermissionsChangeListener.aidl +++ b/core/java/android/permission/IOnPermissionsChangeListener.aidl @@ -21,5 +21,5 @@ package android.permission; * {@hide} */ oneway interface IOnPermissionsChangeListener { - void onPermissionsChanged(int uid, String deviceId); + void onPermissionsChanged(int uid, String persistentDeviceId); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 7a158c548a38..91adc37cb654 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1738,8 +1738,9 @@ public final class PermissionManager { } @Override - public void onPermissionsChanged(int uid, String deviceId) { - mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget(); + public void onPermissionsChanged(int uid, String persistentDeviceId) { + mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId) + .sendToTarget(); } @Override @@ -1747,8 +1748,8 @@ public final class PermissionManager { switch (msg.what) { case MSG_PERMISSIONS_CHANGED: { final int uid = msg.arg1; - final String deviceId = msg.obj.toString(); - mListener.onPermissionsChanged(uid, deviceId); + final String persistentDeviceId = msg.obj.toString(); + mListener.onPermissionsChanged(uid, persistentDeviceId); return true; } default: diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 33c15d775ff1..ff6ec29bb8ac 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -18,6 +18,7 @@ package android.provider; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -36,6 +37,7 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; import android.app.AutomaticZenRule; +import android.app.Flags; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.SearchManager; @@ -1904,6 +1906,36 @@ public final class Settings { = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS"; /** + * Activity Action: Shows the settings page for an {@link AutomaticZenRule} mode. + * <p> + * Users can change the behavior of the mode when it's activated and access the owning app's + * additional configuration screen, where triggering criteria can be modified (see + * {@link AutomaticZenRule#setConfigurationActivity(ComponentName)}). + * <p> + * A matching Activity will only be found if + * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true. + * <p> + * Input: Intent's data URI set with an application name, using the "package" schema (like + * "package:com.my.app"). + * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}. + * <p> + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS + = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS"; + + /** + * Activity Extra: The String id of the {@link AutomaticZenRule mode} settings to display. + * <p> + * This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID + = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID"; + + /** * Activity Action: Show settings for video captioning. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard diff --git a/core/java/android/service/notification/OWNERS b/core/java/android/service/notification/OWNERS index bb0e6aba436b..cb0b5fa6a029 100644 --- a/core/java/android/service/notification/OWNERS +++ b/core/java/android/service/notification/OWNERS @@ -2,6 +2,7 @@ juliacr@google.com yurilin@google.com +matiashe@google.com jeffdq@google.com dsandler@android.com dsandler@google.com diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING index 21a8eab19837..e5910ea4a1fa 100644 --- a/core/java/android/service/timezone/TEST_MAPPING +++ b/core/java/android/service/timezone/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.service." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "CtsLocationTimeZoneManagerHostTest" } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 43bfe139c223..53aed498f110 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -23,7 +23,7 @@ import static java.util.Collections.EMPTY_LIST; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; -import android.annotation.Hide; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -752,6 +752,7 @@ public class AccessibilityNodeInfo implements Parcelable { * {@link #isGranularScrollingSupported()} to check if granular scrolling is supported. * </p> */ + @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING) public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT"; @@ -2608,6 +2609,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return True if all scroll actions that could support * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise. */ + @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING) public boolean isGranularScrollingSupported() { return getBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING); } @@ -2626,6 +2628,7 @@ public class AccessibilityNodeInfo implements Parcelable { * * @throws IllegalStateException If called from an AccessibilityService. */ + @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING) public void setGranularScrollingSupported(boolean granularScrollingSupported) { setBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING, granularScrollingSupported); @@ -6119,6 +6122,7 @@ public class AccessibilityNodeInfo implements Parcelable { * This should be used for {@code mItemCount} and * {@code mImportantForAccessibilityItemCount} when values for those fields are not known. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public static final int UNDEFINED = -1; private int mRowCount; @@ -6229,8 +6233,8 @@ public class AccessibilityNodeInfo implements Parcelable { * the item count is not known. * @param importantForAccessibilityItemCount The count of the collection's views considered * important for accessibility. + * @hide */ - @Hide public CollectionInfo(int rowCount, int columnCount, boolean hierarchical, int selectionMode, int itemCount, int importantForAccessibilityItemCount) { mRowCount = rowCount; @@ -6287,6 +6291,7 @@ public class AccessibilityNodeInfo implements Parcelable { * * @return The count of items, which may be {@code UNDEFINED} if the count is not known. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public int getItemCount() { return mItemCount; } @@ -6297,6 +6302,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return The count of items important for accessibility, which may be {@code UNDEFINED} * if the count is not known. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public int getImportantForAccessibilityItemCount() { return mImportantForAccessibilityItemCount; } @@ -6323,6 +6329,7 @@ public class AccessibilityNodeInfo implements Parcelable { * The builder for CollectionInfo. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public static final class Builder { private int mRowCount = 0; private int mColumnCount = 0; @@ -6334,6 +6341,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Creates a new Builder. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public Builder() { } @@ -6343,6 +6351,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setRowCount(int rowCount) { mRowCount = rowCount; return this; @@ -6354,6 +6363,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setColumnCount(int columnCount) { mColumnCount = columnCount; return this; @@ -6364,6 +6374,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setHierarchical(boolean hierarchical) { mHierarchical = hierarchical; return this; @@ -6375,6 +6386,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setSelectionMode(int selectionMode) { mSelectionMode = selectionMode; return this; @@ -6389,6 +6401,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setItemCount(int itemCount) { mItemCount = itemCount; return this; @@ -6401,6 +6414,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setImportantForAccessibilityItemCount( int importantForAccessibilityItemCount) { mImportantForAccessibilityItemCount = importantForAccessibilityItemCount; @@ -6411,6 +6425,7 @@ public class AccessibilityNodeInfo implements Parcelable { * Creates a new {@link CollectionInfo} instance. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo build() { CollectionInfo collectionInfo = new CollectionInfo(mRowCount, mColumnCount, mHierarchical); diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 950fa4b1d711..c337cb45d3a6 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -17,6 +17,13 @@ flag { } flag { + namespace: "accessibility" + name: "collection_info_item_counts" + description: "Fields for total items and the number of important for accessibility items in a collection" + bug: "302376158" +} + +flag { name: "deduplicate_accessibility_warning_dialog" namespace: "accessibility" description: "Removes duplicate definition of the accessibility warning dialog." @@ -39,6 +46,13 @@ flag { flag { namespace: "accessibility" + name: "granular_scrolling" + description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen" + bug: "302376158" +} + +flag { + namespace: "accessibility" name: "update_always_on_a11y_service" description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut." bug: "298869916" diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 11bd22f7d6ff..0da03fb5aaeb 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -39,4 +39,12 @@ flag { description: "Remove uses of ScreenCapture#captureDisplay" is_fixed_read_only: true bug: "293445881" -}
\ No newline at end of file +} + +flag { + namespace: "window_surfaces" + name: "allow_disable_activity_record_input_sink" + description: "Whether to allow system activity to disable ActivityRecordInputSink" + is_fixed_read_only: true + bug: "262477923" +} diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index e3bb1fe8b736..7be27be2e798 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -54,18 +54,6 @@ public class SystemUiSystemPropertiesFlags { */ public static final class NotificationFlags { - /** - * FOR DEVELOPMENT / TESTING ONLY!!! - * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission. - * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI - */ - public static final Flag FSI_FORCE_DEMOTE = - devFlag("persist.sysui.notification.fsi_force_demote"); - - /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */ - public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI = - releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi"); - /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index c7ab6aa3934e..f5b877a70b84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -963,7 +963,11 @@ public class BubbleExpandedView extends LinearLayout { && mTaskView.isAttachedToWindow()) { // post this to the looper, because if the device orientation just changed, we need to // let the current shell transition complete before updating the task view bounds. - post(() -> mTaskView.onLocationChanged()); + post(() -> { + if (mTaskView != null) { + mTaskView.onLocationChanged(); + } + }); } if (mIsOverflow) { // post this to the looper so that the view has a chance to be laid out before it can diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 27dc870e81ae..b158f88a68c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -250,10 +250,10 @@ public abstract class WMShellBaseModule { SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader, - CompatUIConfiguration compatUIConfiguration, - CompatUIShellCommandHandler compatUIShellCommandHandler, - AccessibilityManager accessibilityManager) { + Lazy<DockStateReader> dockStateReader, + Lazy<CompatUIConfiguration> compatUIConfiguration, + Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler, + Lazy<AccessibilityManager> accessibilityManager) { if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) { return Optional.empty(); } @@ -268,10 +268,10 @@ public abstract class WMShellBaseModule { syncQueue, mainExecutor, transitionsLazy, - dockStateReader, - compatUIConfiguration, - compatUIShellCommandHandler, - accessibilityManager)); + dockStateReader.get(), + compatUIConfiguration.get(), + compatUIShellCommandHandler.get(), + accessibilityManager.get())); } @WMSingleton diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index b63ee1bd3d98..a9d1a2aed8cc 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -25,8 +25,9 @@ #include "MinikinSkia.h" #include "SkPaint.h" -#include "SkStream.h" // Fot tests. +#include "SkStream.h" // For tests. #include "SkTypeface.h" +#include "utils/TypefaceUtils.h" #include <minikin/FontCollection.h> #include <minikin/FontFamily.h> @@ -186,7 +187,9 @@ void Typeface::setRobotoTypefaceForTest() { LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont); void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0); std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size)); - sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData)); + sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); + LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr"); + sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData)); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont); std::shared_ptr<minikin::MinikinFont> font = diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt index 4533db674da2..3abdb6f9c9f2 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt @@ -37,17 +37,17 @@ val Intent.requestInfo: RequestInfo? RequestInfo::class.java ) -val Intent.getCredentialProviderDataList: List<ProviderData> +val Intent.getCredentialProviderDataList: List<GetCredentialProviderData> get() = this.extras?.getParcelableArrayList( ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, GetCredentialProviderData::class.java - ) ?: emptyList() + ) ?.filterIsInstance<GetCredentialProviderData>() ?: emptyList() -val Intent.createCredentialProviderDataList: List<ProviderData> +val Intent.createCredentialProviderDataList: List<CreateCredentialProviderData> get() = this.extras?.getParcelableArrayList( ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, CreateCredentialProviderData::class.java - ) ?: emptyList() + ) ?.filterIsInstance<CreateCredentialProviderData>() ?: emptyList() val Intent.resultReceiver: ResultReceiver? get() = this.getParcelableExtra( diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt index ee45fbb00ba6..d4bca2add6cb 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt @@ -18,7 +18,6 @@ package com.android.credentialmanager.mapper import android.content.Intent import android.credentials.ui.Entry -import android.credentials.ui.GetCredentialProviderData import androidx.credentials.provider.PasswordCredentialEntry import com.android.credentialmanager.factory.fromSlice import com.android.credentialmanager.ktx.getCredentialProviderDataList @@ -32,12 +31,10 @@ import com.google.common.collect.ImmutableMap fun Intent.toGet(): Request.Get { val credentialEntries = mutableListOf<Pair<String, Entry>>() for (providerData in getCredentialProviderDataList) { - if (providerData is GetCredentialProviderData) { - for (credentialEntry in providerData.credentialEntries) { - credentialEntries.add( - Pair(providerData.providerFlattenedComponentName, credentialEntry) - ) - } + for (credentialEntry in providerData.credentialEntries) { + credentialEntries.add( + Pair(providerData.providerFlattenedComponentName, credentialEntry) + ) } } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 2c15fc6a9cfd..8412cbaaea36 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -249,6 +249,8 @@ public class SecureSettings { Settings.Secure.HUB_MODE_TUTORIAL_STATE, Settings.Secure.STYLUS_BUTTONS_ENABLED, Settings.Secure.STYLUS_HANDWRITING_ENABLED, - Settings.Secure.DEFAULT_NOTE_TASK_PROFILE + Settings.Secure.DEFAULT_NOTE_TASK_PROFILE, + Settings.Secure.CREDENTIAL_SERVICE, + Settings.Secure.CREDENTIAL_SERVICE_PRIMARY }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 71c2ddc6de1f..9197554662d3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -19,11 +19,13 @@ package android.provider.settings.validators; import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR; @@ -62,7 +64,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ADAPTIVE_CHARGING_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR); VALIDATORS.put( Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE)); @@ -398,5 +399,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED, new DiscreteValueValidator(new String[] {"-1", "0", "1"})); VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR); + VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR); + VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java index 49012b0159c2..a8a659ee1e5c 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java @@ -235,4 +235,30 @@ public class SettingsValidators { } } }; + + static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null || value.equals("")) { + return true; + } + + return COLON_SEPARATED_COMPONENT_LIST_VALIDATOR.validate(value); + } + }; + + static final Validator AUTOFILL_SERVICE_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null || value.equals("")) { + return true; + } + + if (value.equals("credential-provider")) { + return true; + } + + return NULLABLE_COMPONENT_NAME_VALIDATOR.validate(value); + } + }; } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index f1b53edbb1cd..efed8c3c1ef4 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -851,8 +851,6 @@ public class SettingsBackupTest { Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, Settings.Secure.UI_TRANSLATION_ENABLED, - Settings.Secure.CREDENTIAL_SERVICE, - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED, Settings.Secure.DND_CONFIGS_MIGRATED, Settings.Secure.NAVIGATION_MODE_RESTORE); diff --git a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java index 865f431183c6..3b3bf8ca15f7 100644 --- a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java +++ b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java @@ -340,6 +340,60 @@ public class SettingsValidatorsTest { failIfOffendersPresent(offenders, "Settings.Secure"); } + @Test + public void testCredentialServiceValidator_returnsTrueIfNull() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(null)); + } + + @Test + public void testCredentialServiceValidator_returnsTrueIfEmpty() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("")); + } + + @Test + public void testCredentialServiceValidator_returnsTrueIfSingleComponentName() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate( + "android.credentials/android.credentials.Test")); + } + + @Test + public void testCredentialServiceValidator_returnsTrueIfMultipleComponentName() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate( + "android.credentials/android.credentials.Test" + + ":android.credentials/.Test2")); + } + + @Test + public void testCredentialServiceValidator_returnsFalseIfInvalidComponentName() { + assertFalse(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("test")); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfNull() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(null)); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfEmpty() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("")); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfPlaceholder() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("credential-provider")); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfSingleComponentName() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate( + "android.credentials/android.credentials.Test")); + } + + @Test + public void testAutofillServiceValidator_returnsFalseIfInvalidComponentName() { + assertFalse(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("test")); + } + private void failIfOffendersPresent(String offenders, String settingsType) { if (offenders.length() > 0) { fail("All " + settingsType + " settings that are backed up have to have a non-null" diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 9ab9ba8bbfed..d78038ecee61 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -224,11 +224,6 @@ android_library { extra_check_modules: ["SystemUILintChecker"], warning_checks: ["MissingApacheLicenseDetector"], }, - errorprone: { - javacflags: [ - "-Xep:InvalidPatternSyntax:WARN", - ], - }, } filegroup { @@ -553,11 +548,6 @@ android_library { test: true, extra_check_modules: ["SystemUILintChecker"], }, - errorprone: { - javacflags: [ - "-Xep:InvalidPatternSyntax:WARN", - ], - }, } android_app { @@ -599,12 +589,6 @@ android_app { }, plugins: ["dagger2-compiler"], - - errorprone: { - javacflags: [ - "-Xep:InvalidPatternSyntax:WARN", - ], - }, } android_robolectric_test { diff --git a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml index 952f056b3023..cc99f5e125f3 100644 --- a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml +++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml @@ -22,13 +22,15 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" - android:orientation="horizontal" > + android:orientation="horizontal" + android:theme="@style/Theme.SystemUI.QuickSettings.Header" > <com.android.systemui.util.AutoMarqueeTextView android:id="@+id/mobile_carrier_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:textAppearance="@style/TextAppearance.QS.Status.Carriers" android:layout_marginEnd="@dimen/qs_carrier_margin_width" android:visibility="gone" android:textDirection="locale" diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml index 355e75d0716b..9c08f5ef4cfe 100644 --- a/packages/SystemUI/res/drawable/notification_material_bg.xml +++ b/packages/SystemUI/res/drawable/notification_material_bg.xml @@ -18,7 +18,7 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:color="?android:attr/colorControlHighlight"> - <item> + <item android:id="@+id/notification_background_color_layer"> <shape> <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" /> </shape> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt index 99529a100b07..9a50d8370525 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -49,12 +50,22 @@ constructor( transitionInteractor.startedKeyguardState.map { keyguardState -> keyguardState == KeyguardState.AOD } + + private fun getColor(usingBackgroundProtection: Boolean): Int { + return if (usingBackgroundProtection) { + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + } else { + Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent) + } + } + private val color: Flow<Int> = - configurationRepository.onAnyConfigurationChange - .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) } - .onStart { - emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)) - } + deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection -> + configurationRepository.onAnyConfigurationChange + .map { getColor(useBgProtection) } + .onStart { emit(getColor(useBgProtection)) } + } + private val useAodIconVariant: Flow<Boolean> = combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) { isTransitionToAod, diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 58c4f0d029c7..ef7296743f20 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -131,6 +131,9 @@ class EmojiHelper { + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + "?)*"; + // Not all JDKs support emoji patterns, including the one errorprone runs under, which + // makes it think that this is an invalid pattern. + @SuppressWarnings("InvalidPatternSyntax") static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 8eda96f62257..64f61d9ac2da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -27,6 +27,7 @@ import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -42,9 +43,11 @@ import java.util.Arrays; * A view that can be used for both the dimmed and normal background of an notification. */ public class NotificationBackgroundView extends View implements Dumpable { + private static final String TAG = "NotificationBackgroundView"; private final boolean mDontModifyCorners; private Drawable mBackground; + private Drawable mBackgroundDrawableToTint; private int mClipTopAmount; private int mClipBottomAmount; private int mTintColor; @@ -131,6 +134,7 @@ public class NotificationBackgroundView extends View implements Dumpable { unscheduleDrawable(mBackground); } mBackground = background; + mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground); mRippleColor = null; mBackground.mutate(); if (mBackground != null) { @@ -144,25 +148,46 @@ public class NotificationBackgroundView extends View implements Dumpable { invalidate(); } + // setCustomBackground should be called from ActivatableNotificationView.initBackground + // with R.drawable.notification_material_bg, which is a layer-list with a lower layer + // for the background color (annotated with an ID so we can find it) and an upper layer + // to blend in the stateful @color/notification_overlay_color. + // + // If the notification is tinted, we want to set a tint list on *just that lower layer* that + // will replace the default materialColorSurfaceContainerHigh *without* wiping out the stateful + // tints in the upper layer that make the hovered and pressed states visible. + // + // This function fishes that lower layer out, or makes a fuss in logcat if it can't find it. + private @Nullable Drawable findBackgroundDrawableToTint(@Nullable Drawable background) { + if (background == null) { + return null; + } + + if (!(background instanceof LayerDrawable)) { + Log.wtf(TAG, "background is not a LayerDrawable: " + background); + return background; + } + + final Drawable backgroundColorLayer = ((LayerDrawable) background).findDrawableByLayerId( + R.id.notification_background_color_layer); + + if (backgroundColorLayer == null) { + Log.wtf(TAG, "background is missing background color layer: " + background); + return background; + } + + return backgroundColorLayer; + } + public void setCustomBackground(int drawableResId) { final Drawable d = mContext.getDrawable(drawableResId); setCustomBackground(d); } public void setTint(int tintColor) { - if (tintColor != 0) { - ColorStateList stateList = new ColorStateList(new int[][]{ - new int[]{com.android.internal.R.attr.state_pressed}, - new int[]{com.android.internal.R.attr.state_hovered}, - new int[]{}}, - - new int[]{tintColor, tintColor, tintColor} - ); - mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP); - mBackground.setTintList(stateList); - } else { - mBackground.setTintList(null); - } + mBackgroundDrawableToTint.setTint(tintColor); + mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP); + mTintColor = tintColor; invalidate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 61a79b716b0f..6944453506a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -565,6 +565,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; private final ScreenOffAnimationController mScreenOffAnimationController; private boolean mShouldUseSplitNotificationShade; + private boolean mShouldSkipTopPaddingAnimationAfterFold = false; private boolean mHasFilteredOutSeenNotifications; @Nullable private SplitShadeStateController mSplitShadeStateController = null; private boolean mIsSmallLandscapeLockscreenEnabled = false; @@ -1364,7 +1365,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mTopPadding = topPadding; updateAlgorithmHeightAndPadding(); updateContentHeight(); - if (shouldAnimate && mAnimationsEnabled && mIsExpanded) { + if (mAmbientState.isOnKeyguard() + && !mShouldUseSplitNotificationShade + && mShouldSkipTopPaddingAnimationAfterFold) { + mShouldSkipTopPaddingAnimationAfterFold = false; + } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) { mTopPaddingNeedsAnimation = true; mNeedsAnimation = true; } @@ -5741,6 +5746,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean split = mSplitShadeStateController.shouldUseSplitNotificationShade(getResources()); if (split != mShouldUseSplitNotificationShade) { mShouldUseSplitNotificationShade = split; + mShouldSkipTopPaddingAnimationAfterFold = true; mAmbientState.setUseSplitShade(split); updateDismissBehavior(); updateUseRoundedRectClipping(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt index a7e7dd074a33..2b51ac5e3187 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.animation import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.core.animation.doOnEnd +import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.doOnEnd @@ -30,6 +31,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest @RunWithLooper +@FlakyTest(bugId = 302149604) class AnimatorTestRuleOrderTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index 1c7fd565c289..7361f6baa7b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.interruption import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE @@ -37,7 +36,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() { init { - setFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR) + mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME) } override val provider by lazy { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index df6f0d716577..d2c046c67fb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.interruption import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK @@ -30,7 +29,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() { init { - setFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR) + mSetFlagsRule.enableFlags(VisualInterruptionRefactor.FLAG_NAME) } override val provider by lazy { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 7babff5e0b2e..2ac0cb7499d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -44,7 +44,6 @@ import android.graphics.drawable.Icon import android.hardware.display.FakeAmbientDisplayConfiguration import android.os.Looper import android.os.PowerManager -import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF import android.provider.Settings.Global.HEADS_UP_ON @@ -84,15 +83,10 @@ import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Assert.assertEquals import org.junit.Before -import org.junit.Rule import org.junit.Test import org.mockito.Mockito.`when` as whenever abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { - @JvmField - @Rule - val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) - private val fakeLogBuffer = LogBuffer( name = "FakeLog", diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 4b1c7e8faa38..4422764c4bac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -157,6 +157,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -347,6 +348,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true); when(mDozeParameters.getAlwaysOn()).thenReturn(true); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); + // TODO: b/312476335 - Update to check flag and instantiate old or new implementation. + mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME); IThermalService thermalService = mock(IThermalService.class); mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService, diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index df7609c544a4..200cfd3605c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -146,6 +146,7 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; @@ -366,6 +367,9 @@ public class BubblesTest extends SysuiTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + // TODO: b/312476335 - Update to check flag and instantiate old or new implementation. + mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { doReturn(true).when(mTransitions).isRegistered(); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b9c269c91651..71a1f012e5cb 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -75,7 +75,6 @@ import android.companion.IOnMessageReceivedListener; import android.companion.IOnTransportsChangedListener; import android.companion.ISystemDataTransferCallback; import android.companion.datatransfer.PermissionSyncRequest; -import android.companion.utils.FeatureUtils; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; @@ -829,11 +828,6 @@ public class CompanionDeviceManagerService extends SystemService { @Override public PendingIntent buildPermissionTransferUserConsentIntent(String packageName, int userId, int associationId) { - if (!FeatureUtils.isPermSyncEnabled()) { - throw new UnsupportedOperationException("Calling" - + " buildPermissionTransferUserConsentIntent, but this API is disabled by" - + " the system."); - } return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent( packageName, userId, associationId); } @@ -841,10 +835,6 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void startSystemDataTransfer(String packageName, int userId, int associationId, ISystemDataTransferCallback callback) { - if (!FeatureUtils.isPermSyncEnabled()) { - throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this" - + " API is disabled by the system."); - } mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId, associationId, callback); } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 7907d616d1b5..77b6d583808c 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -1182,8 +1182,8 @@ public class BinaryTransparencyService extends SystemService { // we are only interested in doing things at PHASE_BOOT_COMPLETED if (phase == PHASE_BOOT_COMPLETED) { - Slog.i(TAG, "Boot completed. Getting VBMeta Digest."); - getVBMetaDigestInformation(); + Slog.i(TAG, "Boot completed. Getting boot integrity data."); + collectBootIntegrityInfo(); // Log to statsd // TODO(b/264061957): For now, biometric system properties are always collected if users @@ -1458,10 +1458,19 @@ public class BinaryTransparencyService extends SystemService { } } - private void getVBMetaDigestInformation() { + private void collectBootIntegrityInfo() { mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE); Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest)); FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest); + + if (android.security.Flags.binaryTransparencySepolicyHash()) { + byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes( + "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer()); + String sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false); + Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded); + FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED, + sepolicyHashEncoded, mVbmetaDigest); + } } /** diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bae06347d8a2..0492f4314875 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -828,6 +828,22 @@ public class NotificationManagerService extends SystemService { } } + // Removes all notifications with the specified user & package. + public void removePackageNotifications(String pkg, @UserIdInt int userId) { + synchronized (mBufferLock) { + Iterator<Pair<StatusBarNotification, Integer>> bufferIter = descendingIterator(); + while (bufferIter.hasNext()) { + final Pair<StatusBarNotification, Integer> pair = bufferIter.next(); + if (pair.first != null + && userId == pair.first.getNormalizedUserId() + && pkg != null && pkg.equals(pair.first.getPackageName()) + && pair.first.getNotification() != null) { + bufferIter.remove(); + } + } + } + } + void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) { synchronized (mBufferLock) { Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator(); @@ -1902,7 +1918,6 @@ public class NotificationManagerService extends SystemService { unhideNotificationsForPackages(pkgList, uidList); } } - mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList); } } @@ -4021,11 +4036,8 @@ public class NotificationManagerService extends SystemService { Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e); return false; } - final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags - .SHOW_STICKY_HUN_FOR_DENIED_FSI); return checkUseFullScreenIntentPermission(attributionSource, applicationInfo, - showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */); + false /* forDataDelivery */); } @Override @@ -4219,7 +4231,8 @@ public class NotificationManagerService extends SystemService { boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel( pkg, callingUid, channelId, callingUid, isSystemOrSystemUi); if (previouslyExisted) { - // Remove from both recent notification archive and notification history + // Remove from both recent notification archive (recently dismissed notifications) + // and notification history mArchive.removeChannelNotifications(pkg, callingUser, channelId); mHistoryManager.deleteNotificationChannel(pkg, callingUid, channelId); mListeners.notifyNotificationChannelChanged(pkg, @@ -7274,28 +7287,12 @@ public class NotificationManagerService extends SystemService { notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED; if (notification.fullScreenIntent != null) { - final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE); - if (forceDemoteFsiToStickyHun) { + final AttributionSource attributionSource = + new AttributionSource.Builder(notificationUid).setPackageName(pkg).build(); + final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission( + attributionSource, ai, true /* forDataDelivery */); + if (!canUseFullScreenIntent) { makeStickyHun(notification, pkg, userId); - } else { - final AttributionSource attributionSource = - new AttributionSource.Builder(notificationUid).setPackageName(pkg).build(); - final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags - .SHOW_STICKY_HUN_FOR_DENIED_FSI); - final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission( - attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */, - true /* forDataDelivery */); - if (!canUseFullScreenIntent) { - if (showStickyHunIfDenied) { - makeStickyHun(notification, pkg, userId); - } else { - notification.fullScreenIntent = null; - Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the" - + "USE_FULL_SCREEN_INTENT permission"); - } - } } } @@ -7402,27 +7399,20 @@ public class NotificationManagerService extends SystemService { } private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource, - @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission, + @NonNull ApplicationInfo applicationInfo, boolean forDataDelivery) { if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) { return true; } - if (isAppOpPermission) { - final int permissionResult; - if (forDataDelivery) { - permissionResult = mPermissionManager.checkPermissionForDataDelivery( - permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null); - } else { - permissionResult = mPermissionManager.checkPermissionForPreflight( - permission.USE_FULL_SCREEN_INTENT, attributionSource); - } - return permissionResult == PermissionManager.PERMISSION_GRANTED; + final int permissionResult; + if (forDataDelivery) { + permissionResult = mPermissionManager.checkPermissionForDataDelivery( + permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null); } else { - final int permissionResult = getContext().checkPermission( - permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(), - attributionSource.getUid()); - return permissionResult == PERMISSION_GRANTED; + permissionResult = mPermissionManager.checkPermissionForPreflight( + permission.USE_FULL_SCREEN_INTENT, attributionSource); } + return permissionResult == PermissionManager.PERMISSION_GRANTED; } private void checkRemoteViews(String pkg, String tag, int id, Notification notification) { @@ -9444,7 +9434,11 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < size; i++) { final String pkg = pkgList[i]; final int uid = uidList[i]; - mHistoryManager.onPackageRemoved(UserHandle.getUserId(uid), pkg); + final int userHandle = UserHandle.getUserId(uid); + // Removes this package's notifications from both recent notification archive + // (recently dismissed notifications) and notification history. + mArchive.removePackageNotifications(pkg, userHandle); + mHistoryManager.onPackageRemoved(userHandle, pkg); } } if (preferencesChanged) { diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index d2e980b7e355..9a6ea2c2aeb8 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -530,16 +530,13 @@ interface NotificationRecordLogger { this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter(); this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r); - final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver() - .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI); - final boolean hasFullScreenIntent = p.r.getSbn().getNotification().fullScreenIntent != null; final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0; - this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled, + this.fsi_state = NotificationRecordLogger.getFsiState( hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType); this.is_locked = p.r.isLocked(); @@ -587,13 +584,10 @@ interface NotificationRecordLogger { * @return FrameworkStatsLog enum of the state of the full screen intent posted with this * notification. */ - static int getFsiState(boolean isStickyHunFlagEnabled, - boolean hasFullScreenIntent, + static int getFsiState(boolean hasFullScreenIntent, boolean hasFsiRequestedButDeniedFlag, NotificationReportedEvent eventType) { - - if (!isStickyHunFlagEnabled - || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) { + if (eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) { // Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth, // so we should log 0 when possible. return 0; diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 783e9bbb034f..252664a7e4e4 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -2143,10 +2143,7 @@ public class PreferencesHelper implements RankingConfig { * @return State of the full screen intent permission for this package. */ @VisibleForTesting - int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) { - if (!isFlagEnabled) { - return 0; - } + int getFsiState(String pkg, int uid, boolean requestedFSIPermission) { if (!requestedFSIPermission) { return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; } @@ -2167,10 +2164,8 @@ public class PreferencesHelper implements RankingConfig { * the user. */ @VisibleForTesting - boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags, - boolean isStickyHunFlagEnabled) { - if (!isStickyHunFlagEnabled - || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) { + boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags) { + if (fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) { return false; } return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; @@ -2213,22 +2208,18 @@ public class PreferencesHelper implements RankingConfig { pkgsWithPermissionsToHandle.remove(key); } - final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver() - .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI); - final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission( android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid); - final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission, - isStickyHunFlagEnabled); + final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission); final int currentPermissionFlags = mPm.getPermissionFlags( android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, UserHandle.getUserHandleForUid(r.uid)); final boolean fsiIsUserSet = - isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags, - isStickyHunFlagEnabled); + isFsiPermissionUserSet(r.pkg, r.uid, fsiState, + currentPermissionFlags); events.add(FrameworkStatsLog.buildStatsEvent( PACKAGE_NOTIFICATION_PREFERENCES, diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 79cd2a0b236f..92d469ccbfac 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -259,6 +259,19 @@ public interface Computer extends PackageDataSnapshot { */ boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps, int callingUid, int userId); + + /** + * Different from + * {@link #shouldFilterApplicationIncludingUninstalled(PackageStateInternal, int, int)}, the + * function returns {@code true} if: + * <ul> + * <li>The target package is not archived. + * <li>The package cannot be found in the device or has been uninstalled in the current user. + * </ul> + */ + boolean shouldFilterApplicationIncludingUninstalledNotArchived( + @Nullable PackageStateInternal ps, + int callingUid, int userId); /** * Different from {@link #shouldFilterApplication(SharedUserSetting, int, int)}, the function * returns {@code true} if packages with the same shared user are all uninstalled in the current diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 11a6d1b8f9a4..e5c4ccc73bc3 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -2455,7 +2455,8 @@ public class ComputerEngine implements Computer { */ public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid, @Nullable ComponentName component, - @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) { + @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall, + boolean filterArchived) { if (Process.isSdkSandboxUid(callingUid)) { int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid); // SDK sandbox should be able to see it's client app @@ -2469,14 +2470,20 @@ public class ComputerEngine implements Computer { } final String instantAppPkgName = getInstantAppPackageName(callingUid); final boolean callerIsInstantApp = instantAppPkgName != null; + final boolean packageArchivedForUser = ps != null && PackageArchiver.isArchived( + ps.getUserStateOrDefault(userId)); // Don't treat hiddenUntilInstalled as an uninstalled state, phone app needs to access // these hidden application details to customize carrier apps. Also, allowing the system // caller accessing to application across users. if (ps == null || (filterUninstall - && !isSystemOrRootOrShell(callingUid) - && !ps.isHiddenUntilInstalled() - && !ps.getUserStateOrDefault(userId).isInstalled())) { + && !isSystemOrRootOrShell(callingUid) + && !ps.isHiddenUntilInstalled() + && !ps.getUserStateOrDefault(userId).isInstalled() + // Archived packages behave like uninstalled packages. So if filterUninstall is + // set to true, we dismiss filtering some uninstalled package only if it is + // archived and filterArchived is set as false. + && (!packageArchivedForUser || filterArchived))) { // If caller is instant app or sdk sandbox and ps is null, pretend the application // exists, but, needs to be filtered return (callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid)); @@ -2524,7 +2531,20 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) + */ + public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps, + int callingUid, @Nullable ComponentName component, + @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) { + return shouldFilterApplication( + ps, callingUid, component, componentType, userId, filterUninstall, + true /* filterArchived */); + } + + /** + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid, @Nullable ComponentName component, @@ -2534,7 +2554,8 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplication( @Nullable PackageStateInternal ps, int callingUid, int userId) { @@ -2543,7 +2564,8 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplication(@NonNull SharedUserSetting sus, int callingUid, int userId) { @@ -2558,7 +2580,8 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplicationIncludingUninstalled( @Nullable PackageStateInternal ps, int callingUid, int userId) { @@ -2567,7 +2590,19 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) + */ + public final boolean shouldFilterApplicationIncludingUninstalledNotArchived( + @Nullable PackageStateInternal ps, int callingUid, int userId) { + return shouldFilterApplication( + ps, callingUid, null, TYPE_UNKNOWN, userId, true /* filterUninstall */, + false /* filterArchived */); + } + + /** + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplicationIncludingUninstalled( @NonNull SharedUserSetting sus, int callingUid, int userId) { @@ -5015,7 +5050,7 @@ public class ComputerEngine implements Computer { String installerPackageName = installSource.mInstallerPackageName; if (installerPackageName != null) { final PackageStateInternal ps = mSettings.getPackage(installerPackageName); - if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, + if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid, UserHandle.getUserId(callingUid))) { installerPackageName = null; } @@ -5033,7 +5068,8 @@ public class ComputerEngine implements Computer { return InstallSource.EMPTY; } - if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) { + if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid, + userId)) { return null; } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 65c6329587a5..3b3d79e7dee1 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -440,7 +440,7 @@ final class DeletePackageHelper { if (outInfo != null) { // Remember which users are affected, before the installed states are modified outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL) - ? ps.queryInstalledUsers(allUserHandles, /* installed= */true) + ? ps.queryUsersInstalledOrHasData(allUserHandles) : new int[]{userId}; } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index eff6157380c0..d2a4c2713097 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -37,6 +37,7 @@ import android.app.BroadcastOptions; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedActivityParcel; import android.content.pm.ArchivedPackageParcel; import android.content.pm.LauncherActivityInfo; @@ -140,6 +141,8 @@ public class PackageArchiver { } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "archiveApp"); + verifyUninstallPermissions(); + CompletableFuture<ArchiveState> archiveStateFuture; try { archiveStateFuture = createArchiveState(packageName, userId); @@ -182,6 +185,7 @@ public class PackageArchiver { throws PackageManager.NameNotFoundException { PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(), Binder.getCallingUid(), userId); + verifyNotSystemApp(ps.getFlags()); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); verifyInstaller(responsibleInstallerPackage, userId); verifyOptOutStatus(packageName, @@ -316,6 +320,13 @@ public class PackageArchiver { return intentReceivers != null && !intentReceivers.getList().isEmpty(); } + private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException { + if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || ( + (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { + throw new PackageManager.NameNotFoundException("System apps cannot be archived."); + } + } + /** * Returns true if the app is archivable. */ @@ -337,6 +348,11 @@ public class PackageArchiver { throw new ParcelableException(e); } + if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || ( + (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { + return false; + } + if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) { return false; } @@ -372,9 +388,11 @@ public class PackageArchiver { void requestUnarchive( @NonNull String packageName, @NonNull String callerPackageName, + @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle) { Objects.requireNonNull(packageName); Objects.requireNonNull(callerPackageName); + Objects.requireNonNull(statusReceiver); Objects.requireNonNull(userHandle); Computer snapshot = mPm.snapshotComputer(); @@ -385,6 +403,8 @@ public class PackageArchiver { } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "unarchiveApp"); + verifyInstallPermissions(); + PackageStateInternal ps; try { ps = getPackageState(packageName, snapshot, binderUid, userId); @@ -400,9 +420,12 @@ public class PackageArchiver { packageName))); } + // TODO(b/305902395) Introduce a confirmation dialog if the requestor only holds + // REQUEST_INSTALL permission. int draftSessionId; try { - draftSessionId = createDraftSession(packageName, installerPackage, userId); + draftSessionId = createDraftSession(packageName, installerPackage, statusReceiver, + userId); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { throw ExceptionUtils.wrap((IOException) e.getCause()); @@ -415,11 +438,36 @@ public class PackageArchiver { () -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId)); } - private int createDraftSession(String packageName, String installerPackage, int userId) { + private void verifyInstallPermissions() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) + != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission( + Manifest.permission.REQUEST_INSTALL_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES " + + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request " + + "an unarchival."); + } + } + + private void verifyUninstallPermissions() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES) + != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission( + Manifest.permission.REQUEST_DELETE_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES " + + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request " + + "an archival."); + } + } + + private int createDraftSession(String packageName, String installerPackage, + IntentSender statusReceiver, int userId) { PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); sessionParams.setAppPackageName(packageName); sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT; + sessionParams.unarchiveIntentSender = statusReceiver; + int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId); // Handles case of repeated unarchival calls for the same package. int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid, diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index af43a8bec832..c9663fc30ef5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -16,8 +16,14 @@ package com.android.server.pm; +import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED; +import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR; +import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; @@ -37,6 +43,7 @@ import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; +import android.app.PendingIntent; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; @@ -56,6 +63,7 @@ import android.content.pm.PackageInstaller.InstallConstraints; import android.content.pm.PackageInstaller.InstallConstraintsResult; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageInstaller.UnarchivalStatus; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; @@ -71,6 +79,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.ParcelableException; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteCallbackList; @@ -1630,8 +1639,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void requestUnarchive( @NonNull String packageName, @NonNull String callerPackageName, + @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle) { - mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle); + mPackageArchiver.requestUnarchive(packageName, callerPackageName, statusReceiver, + userHandle); } @Override @@ -1689,6 +1700,102 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + // TODO(b/307299702) Implement error dialog and propagate userActionIntent. + @Override + public void reportUnarchivalStatus( + int unarchiveId, + @UnarchivalStatus int status, + long requiredStorageBytes, + @Nullable PendingIntent userActionIntent, + @NonNull UserHandle userHandle) { + verifyReportUnarchiveStatusInput( + status, requiredStorageBytes, userActionIntent, userHandle); + + int userId = userHandle.getIdentifier(); + int binderUid = Binder.getCallingUid(); + + synchronized (mSessions) { + PackageInstallerSession session = mSessions.get(unarchiveId); + if (session == null || session.userId != userId + || session.params.appPackageName == null) { + throw new ParcelableException(new PackageManager.NameNotFoundException( + TextUtils.formatSimple( + "No valid session with unarchival ID %s found for user %s.", + unarchiveId, userId))); + } + + if (!isCallingUidOwner(session)) { + throw new SecurityException(TextUtils.formatSimple( + "The caller UID %s does not have access to the session with unarchiveId " + + "%d.", + binderUid, unarchiveId)); + } + + IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender; + if (unarchiveIntentSender == null) { + throw new IllegalStateException( + TextUtils.formatSimple( + "Unarchival status for ID %s has already been set or a " + + "session has been created for it already by the " + + "caller.", + unarchiveId)); + } + + // Execute expensive calls outside the sync block. + mPm.mHandler.post( + () -> notifyUnarchivalListener(status, session.params.appPackageName, + unarchiveIntentSender)); + session.params.unarchiveIntentSender = null; + if (status != UNARCHIVAL_OK) { + Binder.withCleanCallingIdentity(session::abandon); + } + } + } + + private static void verifyReportUnarchiveStatusInput(int status, long requiredStorageBytes, + @Nullable PendingIntent userActionIntent, + @NonNull UserHandle userHandle) { + Objects.requireNonNull(userHandle); + if (status == UNARCHIVAL_ERROR_USER_ACTION_NEEDED) { + Objects.requireNonNull(userActionIntent); + } + if (status == UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes <= 0) { + throw new IllegalStateException( + "Insufficient storage error set, but requiredStorageBytes unspecified."); + } + if (status != UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes > 0) { + throw new IllegalStateException( + TextUtils.formatSimple("requiredStorageBytes set, but error is %s.", status) + ); + } + if (!List.of( + UNARCHIVAL_OK, + UNARCHIVAL_ERROR_USER_ACTION_NEEDED, + UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, + UNARCHIVAL_ERROR_NO_CONNECTIVITY, + UNARCHIVAL_GENERIC_ERROR).contains(status)) { + throw new IllegalStateException("Invalid status code passed " + status); + } + } + + private void notifyUnarchivalListener(int status, String packageName, + IntentSender unarchiveIntentSender) { + final Intent fillIn = new Intent(); + fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); + fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status); + // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here. + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_DENIED); + try { + unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null, + /* handler= */ null, /* requiredPermission= */ null, + options.toBundle()); + } catch (SendIntentException e) { + Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e); + } + } + private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, int installerUid) { int count = 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 6f45d2befc6b..f992bd83a8de 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4689,10 +4689,12 @@ class PackageManagerShellCommand extends ShellCommand { final int translatedUserId = translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive"); + final LocalIntentReceiver receiver = new LocalIntentReceiver(); try { mInterface.getPackageInstaller().requestUnarchive(packageName, - /* callerPackageName= */ "", new UserHandle(translatedUserId)); + /* callerPackageName= */ "", receiver.getIntentSender(), + new UserHandle(translatedUserId)); } catch (Exception e) { pw.println("Failure [" + e.getMessage() + "]"); return 1; diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 72090f24a2ed..b50d0a07aa3a 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -779,6 +779,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return readUserState(userId).isInstalled(); } + boolean isArchived(int userId) { + return PackageArchiver.isArchived(readUserState(userId)); + } + int getInstallReason(int userId) { return readUserState(userId).getInstallReason(); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 4e14c908b01b..85563172cf05 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1592,6 +1592,19 @@ public class UserManagerService extends IUserManager.Stub { */ private void showConfirmCredentialToDisableQuietMode( @UserIdInt int userId, @Nullable IntentSender target) { + if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) { + // TODO (b/308121702) It may be brittle to rely on user states to check profile state + int state; + synchronized (mUserStates) { + state = mUserStates.get(userId, UserState.STATE_NONE); + } + if (state != UserState.STATE_NONE) { + Slog.i(LOG_TAG, + "showConfirmCredentialToDisableQuietMode() called too early, user " + userId + + " is still alive."); + return; + } + } // otherwise, we show a profile challenge to trigger decryption of the user final KeyguardManager km = (KeyguardManager) mContext.getSystemService( Context.KEYGUARD_SERVICE); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index ee46ce13ee73..b3672ecb194c 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -16,6 +16,8 @@ package com.android.server.webkit; +import static android.webkit.Flags.updateServiceV2; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -51,7 +53,7 @@ public class WebViewUpdateService extends SystemService { private static final String TAG = "WebViewUpdateService"; private BroadcastReceiver mWebViewUpdatedReceiver; - private WebViewUpdateServiceImpl mImpl; + private WebViewUpdateServiceInterface mImpl; static final int PACKAGE_CHANGED = 0; static final int PACKAGE_ADDED = 1; @@ -60,7 +62,11 @@ public class WebViewUpdateService extends SystemService { public WebViewUpdateService(Context context) { super(context); - mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance()); + if (updateServiceV2()) { + mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance()); + } else { + mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance()); + } } @Override diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 43d62aaa120a..cfdef1471f83 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -63,7 +63,7 @@ import java.util.List; * * @hide */ -class WebViewUpdateServiceImpl { +class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName(); private static class WebViewPackageMissingException extends Exception { @@ -112,7 +112,8 @@ class WebViewUpdateServiceImpl { mSystemInterface = systemInterface; } - void packageStateChanged(String packageName, int changedState, int userId) { + @Override + public void packageStateChanged(String packageName, int changedState, int userId) { // We don't early out here in different cases where we could potentially early-out (e.g. if // we receive PACKAGE_CHANGED for another user than the system user) since that would // complicate this logic further and open up for more edge cases. @@ -163,7 +164,8 @@ class WebViewUpdateServiceImpl { } } - void prepareWebViewInSystemServer() { + @Override + public void prepareWebViewInSystemServer() { mSystemInterface.notifyZygote(isMultiProcessEnabled()); try { synchronized (mLock) { @@ -210,7 +212,8 @@ class WebViewUpdateServiceImpl { mSystemInterface.ensureZygoteStarted(); } - void handleNewUser(int userId) { + @Override + public void handleNewUser(int userId) { // The system user is always started at boot, and by that point we have already run one // round of the package-changing logic (through prepareWebViewInSystemServer()), so early // out here. @@ -218,7 +221,8 @@ class WebViewUpdateServiceImpl { handleUserChange(); } - void handleUserRemoved(int userId) { + @Override + public void handleUserRemoved(int userId) { handleUserChange(); } @@ -232,14 +236,16 @@ class WebViewUpdateServiceImpl { updateCurrentWebViewPackage(null); } - void notifyRelroCreationCompleted() { + @Override + public void notifyRelroCreationCompleted() { synchronized (mLock) { mNumRelroCreationsFinished++; checkIfRelrosDoneLocked(); } } - WebViewProviderResponse waitForAndGetProvider() { + @Override + public WebViewProviderResponse waitForAndGetProvider() { PackageInfo webViewPackage = null; final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS; boolean webViewReady = false; @@ -284,7 +290,8 @@ class WebViewUpdateServiceImpl { * replacing that provider it will not be in use directly, but will be used when the relros * or the replacement are done). */ - String changeProviderAndSetting(String newProviderName) { + @Override + public String changeProviderAndSetting(String newProviderName) { PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName); if (newPackage == null) return ""; return newPackage.packageName; @@ -367,7 +374,8 @@ class WebViewUpdateServiceImpl { /** * Fetch only the currently valid WebView packages. **/ - WebViewProviderInfo[] getValidWebViewPackages() { + @Override + public WebViewProviderInfo[] getValidWebViewPackages() { ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length]; @@ -464,11 +472,13 @@ class WebViewUpdateServiceImpl { return true; } - WebViewProviderInfo[] getWebViewPackages() { + @Override + public WebViewProviderInfo[] getWebViewPackages() { return mSystemInterface.getWebViewPackages(); } - PackageInfo getCurrentWebViewPackage() { + @Override + public PackageInfo getCurrentWebViewPackage() { synchronized (mLock) { return mCurrentWebViewPackage; } @@ -620,7 +630,8 @@ class WebViewUpdateServiceImpl { return null; } - boolean isMultiProcessEnabled() { + @Override + public boolean isMultiProcessEnabled() { int settingValue = mSystemInterface.getMultiProcessSetting(mContext); if (mSystemInterface.isMultiProcessDefaultEnabled()) { // Multiprocess should be enabled unless the user has turned it off manually. @@ -631,7 +642,8 @@ class WebViewUpdateServiceImpl { } } - void enableMultiProcess(boolean enable) { + @Override + public void enableMultiProcess(boolean enable) { PackageInfo current = getCurrentWebViewPackage(); mSystemInterface.setMultiProcessSetting(mContext, enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE); @@ -644,7 +656,8 @@ class WebViewUpdateServiceImpl { /** * Dump the state of this Service. */ - void dumpState(PrintWriter pw) { + @Override + public void dumpState(PrintWriter pw) { pw.println("Current WebView Update Service state"); pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled())); synchronized (mLock) { diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java new file mode 100644 index 000000000000..e618c7e2a80c --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java @@ -0,0 +1,747 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.webkit; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.AsyncTask; +import android.os.Trace; +import android.os.UserHandle; +import android.util.Slog; +import android.webkit.UserPackage; +import android.webkit.WebViewFactory; +import android.webkit.WebViewProviderInfo; +import android.webkit.WebViewProviderResponse; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of the WebViewUpdateService. + * This class doesn't depend on the android system like the actual Service does and can be used + * directly by tests (as long as they implement a SystemInterface). + * + * This class keeps track of and prepares the current WebView implementation, and needs to keep + * track of a couple of different things such as what package is used as WebView implementation. + * + * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI + * thread or on one of multiple Binder threads. The WebView preparation code shares state between + * threads meaning that code that chooses a new WebView implementation or checks which + * implementation is being used needs to hold a lock. + * + * The WebViewUpdateService can be accessed in a couple of different ways. + * 1. It is started from the SystemServer at boot - at that point we just initiate some state such + * as the WebView preparation class. + * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot + * and the WebViewUpdateService should not have been accessed before this call. In this call we + * choose WebView implementation for the first time. + * 3. The update service listens for Intents related to package installs and removals. These intents + * are received and processed on the UI thread. Each intent can result in changing WebView + * implementation. + * 4. The update service can be reached through Binder calls which are handled on specific binder + * threads. These calls can be made from any process. Generally they are used for changing WebView + * implementation (from Settings), getting information about the current WebView implementation (for + * loading WebView into an app process), or notifying the service about Relro creation being + * completed. + * + * @hide + */ +class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { + private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName(); + + private static class WebViewPackageMissingException extends Exception { + WebViewPackageMissingException(String message) { + super(message); + } + + WebViewPackageMissingException(Exception e) { + super(e); + } + } + + private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000. + private static final long NS_PER_MS = 1000000; + + private static final int VALIDITY_OK = 0; + private static final int VALIDITY_INCORRECT_SDK_VERSION = 1; + private static final int VALIDITY_INCORRECT_VERSION_CODE = 2; + private static final int VALIDITY_INCORRECT_SIGNATURE = 3; + private static final int VALIDITY_NO_LIBRARY_FLAG = 4; + + private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE; + private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE; + + private final SystemInterface mSystemInterface; + private final Context mContext; + + private long mMinimumVersionCode = -1; + + // Keeps track of the number of running relro creations + private int mNumRelroCreationsStarted = 0; + private int mNumRelroCreationsFinished = 0; + // Implies that we need to rerun relro creation because we are using an out-of-date package + private boolean mWebViewPackageDirty = false; + private boolean mAnyWebViewInstalled = false; + + private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE; + + // The WebView package currently in use (or the one we are preparing). + private PackageInfo mCurrentWebViewPackage = null; + + private final Object mLock = new Object(); + + WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) { + mContext = context; + mSystemInterface = systemInterface; + } + + @Override + public void packageStateChanged(String packageName, int changedState, int userId) { + // We don't early out here in different cases where we could potentially early-out (e.g. if + // we receive PACKAGE_CHANGED for another user than the system user) since that would + // complicate this logic further and open up for more edge cases. + for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) { + String webviewPackage = provider.packageName; + + if (webviewPackage.equals(packageName)) { + boolean updateWebView = false; + boolean removedOrChangedOldPackage = false; + String oldProviderName = null; + PackageInfo newPackage = null; + synchronized (mLock) { + try { + newPackage = findPreferredWebViewPackage(); + if (mCurrentWebViewPackage != null) { + oldProviderName = mCurrentWebViewPackage.packageName; + } + // Only trigger update actions if the updated package is the one + // that will be used, or the one that was in use before the + // update, or if we haven't seen a valid WebView package before. + updateWebView = + provider.packageName.equals(newPackage.packageName) + || provider.packageName.equals(oldProviderName) + || mCurrentWebViewPackage == null; + // We removed the old package if we received an intent to remove + // or replace the old package. + removedOrChangedOldPackage = + provider.packageName.equals(oldProviderName); + if (updateWebView) { + onWebViewProviderChanged(newPackage); + } + } catch (WebViewPackageMissingException e) { + mCurrentWebViewPackage = null; + Slog.e(TAG, "Could not find valid WebView package to create relro with " + + e); + } + } + if (updateWebView && !removedOrChangedOldPackage + && oldProviderName != null) { + // If the provider change is the result of adding or replacing a + // package that was not the previous provider then we must kill + // packages dependent on the old package ourselves. The framework + // only kills dependents of packages that are being removed. + mSystemInterface.killPackageDependents(oldProviderName); + } + return; + } + } + } + + @Override + public void prepareWebViewInSystemServer() { + mSystemInterface.notifyZygote(isMultiProcessEnabled()); + try { + synchronized (mLock) { + mCurrentWebViewPackage = findPreferredWebViewPackage(); + String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext); + if (userSetting != null + && !userSetting.equals(mCurrentWebViewPackage.packageName)) { + // Don't persist the user-chosen setting across boots if the package being + // chosen is not used (could be disabled or uninstalled) so that the user won't + // be surprised by the device switching to using a certain webview package, + // that was uninstalled/disabled a long time ago, if it is installed/enabled + // again. + mSystemInterface.updateUserSetting(mContext, + mCurrentWebViewPackage.packageName); + } + onWebViewProviderChanged(mCurrentWebViewPackage); + } + } catch (Throwable t) { + // Log and discard errors at this stage as we must not crash the system server. + Slog.e(TAG, "error preparing webview provider from system server", t); + } + + if (getCurrentWebViewPackage() == null) { + // We didn't find a valid WebView implementation. Try explicitly re-enabling the + // fallback package for all users in case it was disabled, even if we already did the + // one-time migration before. If this actually changes the state, we will see the + // PackageManager broadcast shortly and try again. + WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); + WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); + if (fallbackProvider != null) { + Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName); + mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName, + true); + } else { + Slog.e(TAG, "No valid provider and no fallback available."); + } + } + } + + private void startZygoteWhenReady() { + // Wait on a background thread for RELRO creation to be done. We ignore the return value + // because even if RELRO creation failed we still want to start the zygote. + waitForAndGetProvider(); + mSystemInterface.ensureZygoteStarted(); + } + + @Override + public void handleNewUser(int userId) { + // The system user is always started at boot, and by that point we have already run one + // round of the package-changing logic (through prepareWebViewInSystemServer()), so early + // out here. + if (userId == UserHandle.USER_SYSTEM) return; + handleUserChange(); + } + + @Override + public void handleUserRemoved(int userId) { + handleUserChange(); + } + + /** + * Called when a user was added or removed to ensure WebView preparation is triggered. + * This has to be done since the WebView package we use depends on the enabled-state + * of packages for all users (so adding or removing a user might cause us to change package). + */ + private void handleUserChange() { + // Potentially trigger package-changing logic. + updateCurrentWebViewPackage(null); + } + + @Override + public void notifyRelroCreationCompleted() { + synchronized (mLock) { + mNumRelroCreationsFinished++; + checkIfRelrosDoneLocked(); + } + } + + @Override + public WebViewProviderResponse waitForAndGetProvider() { + PackageInfo webViewPackage = null; + final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS; + boolean webViewReady = false; + int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS; + synchronized (mLock) { + webViewReady = webViewIsReadyLocked(); + while (!webViewReady) { + final long timeNowMs = System.nanoTime() / NS_PER_MS; + if (timeNowMs >= timeoutTimeMs) break; + try { + mLock.wait(timeoutTimeMs - timeNowMs); + } catch (InterruptedException e) { + // ignore + } + webViewReady = webViewIsReadyLocked(); + } + // Make sure we return the provider that was used to create the relro file + webViewPackage = mCurrentWebViewPackage; + if (webViewReady) { + // success + } else if (!mAnyWebViewInstalled) { + webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES; + } else { + // Either the current relro creation isn't done yet, or the new relro creatioin + // hasn't kicked off yet (the last relro creation used an out-of-date WebView). + webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO; + String timeoutError = "Timed out waiting for relro creation, relros started " + + mNumRelroCreationsStarted + + " relros finished " + mNumRelroCreationsFinished + + " package dirty? " + mWebViewPackageDirty; + Slog.e(TAG, timeoutError); + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError); + } + } + if (!webViewReady) Slog.w(TAG, "creating relro file timed out"); + return new WebViewProviderResponse(webViewPackage, webViewStatus); + } + + /** + * Change WebView provider and provider setting and kill packages using the old provider. + * Return the new provider (in case we are in the middle of creating relro files, or + * replacing that provider it will not be in use directly, but will be used when the relros + * or the replacement are done). + */ + @Override + public String changeProviderAndSetting(String newProviderName) { + PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName); + if (newPackage == null) return ""; + return newPackage.packageName; + } + + /** + * Update the current WebView package. + * @param newProviderName the package to switch to, null if no package has been explicitly + * chosen. + */ + private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) { + PackageInfo oldPackage = null; + PackageInfo newPackage = null; + boolean providerChanged = false; + synchronized (mLock) { + oldPackage = mCurrentWebViewPackage; + + if (newProviderName != null) { + mSystemInterface.updateUserSetting(mContext, newProviderName); + } + + try { + newPackage = findPreferredWebViewPackage(); + providerChanged = (oldPackage == null) + || !newPackage.packageName.equals(oldPackage.packageName); + } catch (WebViewPackageMissingException e) { + // If updated the Setting but don't have an installed WebView package, the + // Setting will be used when a package is available. + mCurrentWebViewPackage = null; + Slog.e(TAG, "Couldn't find WebView package to use " + e); + return null; + } + // Perform the provider change if we chose a new provider + if (providerChanged) { + onWebViewProviderChanged(newPackage); + } + } + // Kill apps using the old provider only if we changed provider + if (providerChanged && oldPackage != null) { + mSystemInterface.killPackageDependents(oldPackage.packageName); + } + // Return the new provider, this is not necessarily the one we were asked to switch to, + // but the persistent setting will now be pointing to the provider we were asked to + // switch to anyway. + return newPackage; + } + + /** + * This is called when we change WebView provider, either when the current provider is + * updated or a new provider is chosen / takes precedence. + */ + private void onWebViewProviderChanged(PackageInfo newPackage) { + synchronized (mLock) { + mAnyWebViewInstalled = true; + if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { + mCurrentWebViewPackage = newPackage; + + // The relro creations might 'finish' (not start at all) before + // WebViewFactory.onWebViewProviderChanged which means we might not know the + // number of started creations before they finish. + mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN; + mNumRelroCreationsFinished = 0; + mNumRelroCreationsStarted = + mSystemInterface.onWebViewProviderChanged(newPackage); + // If the relro creations finish before we know the number of started creations + // we will have to do any cleanup/notifying here. + checkIfRelrosDoneLocked(); + } else { + mWebViewPackageDirty = true; + } + } + + // Once we've notified the system that the provider has changed and started RELRO creation, + // try to restart the zygote so that it will be ready when apps use it. + if (isMultiProcessEnabled()) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady); + } + } + + /** + * Fetch only the currently valid WebView packages. + **/ + @Override + public WebViewProviderInfo[] getValidWebViewPackages() { + ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); + WebViewProviderInfo[] providers = + new WebViewProviderInfo[providersAndPackageInfos.length]; + for (int n = 0; n < providersAndPackageInfos.length; n++) { + providers[n] = providersAndPackageInfos[n].provider; + } + return providers; + } + + private static class ProviderAndPackageInfo { + public final WebViewProviderInfo provider; + public final PackageInfo packageInfo; + + ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) { + this.provider = provider; + this.packageInfo = packageInfo; + } + } + + private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() { + WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages(); + List<ProviderAndPackageInfo> providers = new ArrayList<>(); + for (int n = 0; n < allProviders.length; n++) { + try { + PackageInfo packageInfo = + mSystemInterface.getPackageInfoForProvider(allProviders[n]); + if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) { + providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo)); + } + } catch (NameNotFoundException e) { + // Don't add non-existent packages + } + } + return providers.toArray(new ProviderAndPackageInfo[providers.size()]); + } + + /** + * Returns either the package info of the WebView provider determined in the following way: + * If the user has chosen a provider then use that if it is valid, + * otherwise use the first package in the webview priority list that is valid. + * + */ + private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException { + ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos(); + + String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext); + + // If the user has chosen provider, use that (if it's installed and enabled for all + // users). + for (ProviderAndPackageInfo providerAndPackage : providers) { + if (providerAndPackage.provider.packageName.equals(userChosenProvider)) { + // userPackages can contain null objects. + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, + providerAndPackage.provider); + if (isInstalledAndEnabledForAllUsers(userPackages)) { + return providerAndPackage.packageInfo; + } + } + } + + // User did not choose, or the choice failed; use the most stable provider that is + // installed and enabled for all users, and available by default (not through + // user choice). + for (ProviderAndPackageInfo providerAndPackage : providers) { + if (providerAndPackage.provider.availableByDefault) { + // userPackages can contain null objects. + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, + providerAndPackage.provider); + if (isInstalledAndEnabledForAllUsers(userPackages)) { + return providerAndPackage.packageInfo; + } + } + } + + // This should never happen during normal operation (only with modified system images). + mAnyWebViewInstalled = false; + throw new WebViewPackageMissingException("Could not find a loadable WebView package"); + } + + /** + * Return true iff {@param packageInfos} point to only installed and enabled packages. + * The given packages {@param packageInfos} should all be pointing to the same package, but each + * PackageInfo representing a different user's package. + */ + private static boolean isInstalledAndEnabledForAllUsers( + List<UserPackage> userPackages) { + for (UserPackage userPackage : userPackages) { + if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) { + return false; + } + } + return true; + } + + @Override + public WebViewProviderInfo[] getWebViewPackages() { + return mSystemInterface.getWebViewPackages(); + } + + @Override + public PackageInfo getCurrentWebViewPackage() { + synchronized (mLock) { + return mCurrentWebViewPackage; + } + } + + /** + * Returns whether WebView is ready and is not going to go through its preparation phase + * again directly. + */ + private boolean webViewIsReadyLocked() { + return !mWebViewPackageDirty + && (mNumRelroCreationsStarted == mNumRelroCreationsFinished) + // The current package might be replaced though we haven't received an intent + // declaring this yet, the following flag makes anyone loading WebView to wait in + // this case. + && mAnyWebViewInstalled; + } + + private void checkIfRelrosDoneLocked() { + if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { + if (mWebViewPackageDirty) { + mWebViewPackageDirty = false; + // If we have changed provider since we started the relro creation we need to + // redo the whole process using the new package instead. + try { + PackageInfo newPackage = findPreferredWebViewPackage(); + onWebViewProviderChanged(newPackage); + } catch (WebViewPackageMissingException e) { + mCurrentWebViewPackage = null; + // If we can't find any valid WebView package we are now in a state where + // mAnyWebViewInstalled is false, so loading WebView will be blocked and we + // should simply wait until we receive an intent declaring a new package was + // installed. + } + } else { + mLock.notifyAll(); + } + } + } + + private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) { + // Ensure the provider targets this framework release (or a later one). + if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) { + return VALIDITY_INCORRECT_SDK_VERSION; + } + if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode()) + && !mSystemInterface.systemIsDebuggable()) { + // Webview providers may be downgraded arbitrarily low, prevent that by enforcing + // minimum version code. This check is only enforced for user builds. + return VALIDITY_INCORRECT_VERSION_CODE; + } + if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) { + return VALIDITY_INCORRECT_SIGNATURE; + } + if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) { + return VALIDITY_NO_LIBRARY_FLAG; + } + return VALIDITY_OK; + } + + /** + * Both versionCodes should be from a WebView provider package implemented by Chromium. + * VersionCodes from other kinds of packages won't make any sense in this method. + * + * An introduction to Chromium versionCode scheme: + * "BBBBPPPXX" + * BBBB: 4 digit branch number. It monotonically increases over time. + * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits + * may change their meaning in the future. + * XX: Digits to differentiate different APK builds of the same source version. + * + * This method takes the "BBBB" of versionCodes and compare them. + * + * https://www.chromium.org/developers/version-numbers describes general Chromium versioning; + * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py + * is the canonical source for how Chromium versionCodes are calculated. + * + * @return true if versionCode1 is higher than or equal to versionCode2. + */ + private static boolean versionCodeGE(long versionCode1, long versionCode2) { + long v1 = versionCode1 / 100000; + long v2 = versionCode2 / 100000; + + return v1 >= v2; + } + + /** + * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode + * of all available-by-default WebView provider packages. If there is no such WebView provider + * package on the system, then return -1, which means all positive versionCode WebView packages + * are accepted. + * + * Note that this is a private method that handles a variable (mMinimumVersionCode) which is + * shared between threads. Furthermore, this method does not hold mLock meaning that we must + * take extra care to ensure this method is thread-safe. + */ + private long getMinimumVersionCode() { + if (mMinimumVersionCode > 0) { + return mMinimumVersionCode; + } + + long minimumVersionCode = -1; + for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) { + if (provider.availableByDefault) { + try { + long versionCode = + mSystemInterface.getFactoryPackageVersion(provider.packageName); + if (minimumVersionCode < 0 || versionCode < minimumVersionCode) { + minimumVersionCode = versionCode; + } + } catch (NameNotFoundException e) { + // Safe to ignore. + } + } + } + + mMinimumVersionCode = minimumVersionCode; + return mMinimumVersionCode; + } + + private static boolean providerHasValidSignature(WebViewProviderInfo provider, + PackageInfo packageInfo, SystemInterface systemInterface) { + // Skip checking signatures on debuggable builds, for development purposes. + if (systemInterface.systemIsDebuggable()) return true; + + // Allow system apps to be valid providers regardless of signature. + if (packageInfo.applicationInfo.isSystemApp()) return true; + + // We don't support packages with multiple signatures. + if (packageInfo.signatures.length != 1) return false; + + // If any of the declared signatures match the package signature, it's valid. + for (Signature signature : provider.signatures) { + if (signature.equals(packageInfo.signatures[0])) return true; + } + + return false; + } + + /** + * Returns the only fallback provider in the set of given packages, or null if there is none. + */ + private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) { + for (WebViewProviderInfo provider : webviewPackages) { + if (provider.isFallback) { + return provider; + } + } + return null; + } + + @Override + public boolean isMultiProcessEnabled() { + int settingValue = mSystemInterface.getMultiProcessSetting(mContext); + if (mSystemInterface.isMultiProcessDefaultEnabled()) { + // Multiprocess should be enabled unless the user has turned it off manually. + return settingValue > MULTIPROCESS_SETTING_OFF_VALUE; + } else { + // Multiprocess should not be enabled, unless the user has turned it on manually. + return settingValue >= MULTIPROCESS_SETTING_ON_VALUE; + } + } + + @Override + public void enableMultiProcess(boolean enable) { + PackageInfo current = getCurrentWebViewPackage(); + mSystemInterface.setMultiProcessSetting(mContext, + enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE); + mSystemInterface.notifyZygote(enable); + if (current != null) { + mSystemInterface.killPackageDependents(current.packageName); + } + } + + /** + * Dump the state of this Service. + */ + @Override + public void dumpState(PrintWriter pw) { + pw.println("Current WebView Update Service state"); + pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled())); + synchronized (mLock) { + if (mCurrentWebViewPackage == null) { + pw.println(" Current WebView package is null"); + } else { + pw.println(String.format(" Current WebView package (name, version): (%s, %s)", + mCurrentWebViewPackage.packageName, + mCurrentWebViewPackage.versionName)); + } + pw.println(String.format(" Minimum targetSdkVersion: %d", + UserPackage.MINIMUM_SUPPORTED_SDK)); + pw.println(String.format(" Minimum WebView version code: %d", + mMinimumVersionCode)); + pw.println(String.format(" Number of relros started: %d", + mNumRelroCreationsStarted)); + pw.println(String.format(" Number of relros finished: %d", + mNumRelroCreationsFinished)); + pw.println(String.format(" WebView package dirty: %b", mWebViewPackageDirty)); + pw.println(String.format(" Any WebView package installed: %b", + mAnyWebViewInstalled)); + + try { + PackageInfo preferredWebViewPackage = findPreferredWebViewPackage(); + pw.println(String.format( + " Preferred WebView package (name, version): (%s, %s)", + preferredWebViewPackage.packageName, + preferredWebViewPackage.versionName)); + } catch (WebViewPackageMissingException e) { + pw.println(String.format(" Preferred WebView package: none")); + } + + dumpAllPackageInformationLocked(pw); + } + } + + private void dumpAllPackageInformationLocked(PrintWriter pw) { + WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages(); + pw.println(" WebView packages:"); + for (WebViewProviderInfo provider : allProviders) { + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider); + PackageInfo systemUserPackageInfo = + userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo(); + if (systemUserPackageInfo == null) { + pw.println(String.format(" %s is NOT installed.", provider.packageName)); + continue; + } + + int validity = validityResult(provider, systemUserPackageInfo); + String packageDetails = String.format( + "versionName: %s, versionCode: %d, targetSdkVersion: %d", + systemUserPackageInfo.versionName, + systemUserPackageInfo.getLongVersionCode(), + systemUserPackageInfo.applicationInfo.targetSdkVersion); + if (validity == VALIDITY_OK) { + boolean installedForAllUsers = isInstalledAndEnabledForAllUsers( + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider)); + pw.println(String.format( + " Valid package %s (%s) is %s installed/enabled for all users", + systemUserPackageInfo.packageName, + packageDetails, + installedForAllUsers ? "" : "NOT")); + } else { + pw.println(String.format(" Invalid package %s (%s), reason: %s", + systemUserPackageInfo.packageName, + packageDetails, + getInvalidityReason(validity))); + } + } + } + + private static String getInvalidityReason(int invalidityReason) { + switch (invalidityReason) { + case VALIDITY_INCORRECT_SDK_VERSION: + return "SDK version too low"; + case VALIDITY_INCORRECT_VERSION_CODE: + return "Version code too low"; + case VALIDITY_INCORRECT_SIGNATURE: + return "Incorrect signature"; + case VALIDITY_NO_LIBRARY_FLAG: + return "No WebView-library manifest flag"; + default: + return "Unexcepted validity-reason"; + } + } +} diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java new file mode 100644 index 000000000000..a9c3dc45842f --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.webkit; + +import android.content.pm.PackageInfo; +import android.webkit.WebViewProviderInfo; +import android.webkit.WebViewProviderResponse; + +import java.io.PrintWriter; + +interface WebViewUpdateServiceInterface { + void packageStateChanged(String packageName, int changedState, int userId); + + void handleNewUser(int userId); + + void handleUserRemoved(int userId); + + WebViewProviderInfo[] getWebViewPackages(); + + void prepareWebViewInSystemServer(); + + void notifyRelroCreationCompleted(); + + WebViewProviderResponse waitForAndGetProvider(); + + String changeProviderAndSetting(String newProviderName); + + WebViewProviderInfo[] getValidWebViewPackages(); + + PackageInfo getCurrentWebViewPackage(); + + boolean isMultiProcessEnabled(); + + void enableMultiProcess(boolean enable); + + void dumpState(PrintWriter pw); +} diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig new file mode 100644 index 000000000000..1411acc4ab84 --- /dev/null +++ b/services/core/java/com/android/server/webkit/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.webkit" + +flag { + name: "update_service_v2" + namespace: "webview" + description: "Using a new version of the WebView update service" + bug: "308907090" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index faccca8981c8..315e7d85df24 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -55,6 +55,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH; import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller; +import static com.android.window.flags.Flags.allowDisableActivityRecordInputSink; import android.Manifest; import android.annotation.ColorInt; @@ -1688,4 +1689,20 @@ class ActivityClientController extends IActivityClientController.Stub { return r.mRequestedLaunchingTaskFragmentToken == taskFragmentToken; } } + + @Override + public void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) { + if (!allowDisableActivityRecordInputSink()) { + return; + } + + mService.mAmInternal.enforceCallingPermission( + Manifest.permission.INTERNAL_SYSTEM_WINDOW, "setActivityRecordInputSinkEnabled"); + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); + if (r != null) { + r.mActivityRecordInputSinkEnabled = enabled; + } + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 081759d563a5..d90d4ff6bbd6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -970,6 +970,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mWaitForEnteringPinnedMode; final ActivityRecordInputSink mActivityRecordInputSink; + // System activities with INTERNAL_SYSTEM_WINDOW can disable ActivityRecordInputSink. + boolean mActivityRecordInputSinkEnabled = true; // Activities with this uid are allowed to not create an input sink while being in the same // task and directly above this ActivityRecord. This field is updated whenever a new activity diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index be7d9b63f779..c61d86355c8b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -86,7 +86,8 @@ class ActivityRecordInputSink { final boolean allowPassthrough = activityBelowInTask != null && ( activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid() || activityBelowInTask.isUid(mActivityRecord.getUid())); - if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()) { + if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition() + || !mActivityRecord.mActivityRecordInputSinkEnabled) { mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE); } else { diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 0d196b48b3f2..7c539502461b 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -2175,9 +2175,9 @@ class PermissionService(private val service: AccessCheckingService) : userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { _, - deviceId, + persistentDeviceId, devicePermissionFlags -> - println("Permissions (Device $deviceId):") + println("Permissions (Device $persistentDeviceId):") withIndent { devicePermissionFlags.forEachIndexed { _, permissionName, flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) @@ -2658,7 +2658,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun onDevicePermissionFlagsChanged( appId: Int, userId: Int, - deviceId: String, + persistentDeviceId: String, permissionName: String, oldFlags: Int, newFlags: Int @@ -2681,7 +2681,8 @@ class PermissionService(private val service: AccessCheckingService) : permissionName in NOTIFICATIONS_PERMISSIONS && runtimePermissionRevokedUids.get(uid, true) } - runtimePermissionChangedUidDevices.getOrPut(uid) { mutableSetOf() } += deviceId + runtimePermissionChangedUidDevices + .getOrPut(uid) { mutableSetOf() } += persistentDeviceId } if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) { @@ -2695,9 +2696,9 @@ class PermissionService(private val service: AccessCheckingService) : isPermissionFlagsChanged = false } - runtimePermissionChangedUidDevices.forEachIndexed { _, uid, deviceIds -> - deviceIds.forEach { deviceId -> - onPermissionsChangeListeners.onPermissionsChanged(uid, deviceId) + runtimePermissionChangedUidDevices.forEachIndexed { _, uid, persistentDeviceIds -> + persistentDeviceIds.forEach { persistentDeviceId -> + onPermissionsChangeListeners.onPermissionsChanged(uid, persistentDeviceId) } } runtimePermissionChangedUidDevices.clear() @@ -2772,16 +2773,16 @@ class PermissionService(private val service: AccessCheckingService) : when (msg.what) { MSG_ON_PERMISSIONS_CHANGED -> { val uid = msg.arg1 - val deviceId = msg.obj as String - handleOnPermissionsChanged(uid, deviceId) + val persistentDeviceId = msg.obj as String + handleOnPermissionsChanged(uid, persistentDeviceId) } } } - private fun handleOnPermissionsChanged(uid: Int, deviceId: String) { + private fun handleOnPermissionsChanged(uid: Int, persistentDeviceId: String) { listeners.broadcast { listener -> try { - listener.onPermissionsChanged(uid, deviceId) + listener.onPermissionsChanged(uid, persistentDeviceId) } catch (e: RemoteException) { Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e) } @@ -2796,9 +2797,10 @@ class PermissionService(private val service: AccessCheckingService) : listeners.unregister(listener) } - fun onPermissionsChanged(uid: Int, deviceId: String) { + fun onPermissionsChanged(uid: Int, persistentDeviceId: String) { if (listeners.registeredCallbackCount > 0) { - obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget() + obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId) + .sendToTarget() } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index a9f5b14fc48d..18a2accf071d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.ComponentName; @@ -171,6 +172,12 @@ public class PackageArchiverTest { when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager); when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100); + when(mContext.checkCallingOrSelfPermission( + eq(Manifest.permission.REQUEST_INSTALL_PACKAGES))).thenReturn( + PackageManager.PERMISSION_DENIED); + when(mContext.checkCallingOrSelfPermission( + eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn( + PackageManager.PERMISSION_DENIED); when(mAppOpsManager.checkOp( eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), @@ -386,7 +393,7 @@ public class PackageArchiverTest { Exception e = assertThrows( SecurityException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, "different", - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e).hasMessageThat().isEqualTo( String.format( "The UID %s of callerPackageName set by the caller doesn't match the " @@ -404,7 +411,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s not found.", PACKAGE)); @@ -416,7 +423,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s is not currently archived.", PACKAGE)); @@ -428,7 +435,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s is not currently archived.", PACKAGE)); @@ -452,7 +459,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("No installer found to unarchive app %s.", PACKAGE)); @@ -462,7 +469,8 @@ public class PackageArchiverTest { public void unarchiveApp_success() { mUserState.setArchiveState(createArchiveState()).setInstalled(false); - mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT); + mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT); rule.mocks().getHandler().flush(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java index 4b6183dc9ffa..8bc027d50a3b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; @@ -39,6 +40,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.UiServiceTestCase; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -55,6 +57,9 @@ import java.util.concurrent.atomic.AtomicBoolean; @SmallTest @RunWith(AndroidJUnit4.class) public class ArchiveTest extends UiServiceTestCase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final int SIZE = 5; private NotificationManagerService.Archive mArchive; @@ -249,4 +254,29 @@ public class ArchiveTest extends UiServiceTestCase { assertThat(expected).contains(sbn.getKey()); } } + + @Test + public void testRemoveNotificationsByPackage() { + List<String> expected = new ArrayList<>(); + + StatusBarNotification sbn_remove = getNotification("pkg_remove", 0, + UserHandle.of(USER_CURRENT)); + mArchive.record(sbn_remove, REASON_CANCEL); + + StatusBarNotification sbn_keep = getNotification("pkg_keep", 1, + UserHandle.of(USER_CURRENT)); + mArchive.record(sbn_keep, REASON_CANCEL); + expected.add(sbn_keep.getKey()); + + StatusBarNotification sbn_remove2 = getNotification("pkg_remove", 2, + UserHandle.of(USER_CURRENT)); + mArchive.record(sbn_remove2, REASON_CANCEL); + + mArchive.removePackageNotifications("pkg_remove", USER_CURRENT); + List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true)); + assertThat(actual).hasSize(expected.size()); + for (StatusBarNotification sbn : actual) { + assertThat(expected).contains(sbn.getKey()); + } + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 7fb8b30dea06..b45dcd4d5403 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -65,6 +65,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; +import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -72,7 +73,6 @@ import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; -import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; @@ -87,8 +87,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; @@ -309,7 +307,6 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; - @SmallTest @RunWith(AndroidTestingRunner.class) @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @@ -739,9 +736,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { clearInvocations(mRankingHandler); when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false); - mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false); - var checker = mock(TestableNotificationManagerService.ComponentPermissionChecker.class); mService.permissionChecker = checker; when(checker.check(anyString(), anyInt(), anyInt(), anyBoolean())) @@ -818,6 +812,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver.onReceive(getContext(), intent); } + private void simulatePackageRemovedBroadcast(String pkg, int uid) { + // mimics receive broadcast that package is removed, but doesn't remove the package. + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, + new String[]{pkg}); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid}); + + final Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED); + intent.setData(Uri.parse("package:" + pkg)); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } + private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) { // mimics receive broadcast that package is (un)distracting // but does not actually register that info with packagemanager @@ -883,6 +891,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel.setAllowBubbles(channelEnabled); } + private void setUpPrefsForHistory(int uid, boolean globalEnabled) { + // Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid); + // Sets NOTIFICATION_HISTORY_ENABLED setting for uid 0 + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0); + + // Forces an update by calling observe on mSettingsObserver, which picks up the settings + // changes above. + mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper); + + assertEquals(globalEnabled, Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, uid) != 0); + } + private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) { Notification.Builder nb = new Notification.Builder(mContext, "a") .setContentTitle("foo") @@ -9836,6 +9860,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testHandleOnPackageRemoved_ClearsHistory() throws RemoteException { + // Enables Notification History setting + setUpPrefsForHistory(mUid, true /* =enabled */); + + // Posts a notification to the mTestNotificationChannel. + final NotificationRecord notif = generateNotificationRecord( + mTestNotificationChannel, 1, null, false); + mService.addNotification(notif); + StatusBarNotification[] notifs = mBinderService.getActiveNotifications( + notif.getSbn().getPackageName()); + assertEquals(1, notifs.length); + + // Cancels all notifications. + mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, + notif.getUserId(), REASON_CANCEL); + waitForIdle(); + notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); + assertEquals(0, notifs.length); + + // Checks that notification history's recently canceled archive contains the notification. + notifs = mBinderService.getHistoricalNotificationsWithAttribution(PKG, + mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */); + waitForIdle(); + assertEquals(1, notifs.length); + + // Remove sthe package that contained the channel + simulatePackageRemovedBroadcast(PKG, mUid); + waitForIdle(); + + // Checks that notification history no longer contains the notification. + notifs = mBinderService.getHistoricalNotificationsWithAttribution( + PKG, mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */); + waitForIdle(); + assertEquals(0, notifs.length); + } + + @Test public void testNotificationHistory_addNoisyNotification() throws Exception { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, null /* tvExtender */); @@ -11472,14 +11533,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class)); } - private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested, + private void verifyStickyHun(int permissionState, boolean appRequested, boolean isSticky) throws Exception { when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT, PKG, mUserId)).thenReturn(appRequested); - mTestFlagResolver.setFlagOverride(flag, true); - when(mPermissionManager.checkPermissionForDataDelivery( eq(Manifest.permission.USE_FULL_SCREEN_INTENT), any(), any())) .thenReturn(permissionState); @@ -11503,8 +11562,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testFixNotification_flagEnableStickyHun_fsiPermissionHardDenied_showStickyHun() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true, /* isSticky= */ true); } @@ -11512,16 +11570,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testFixNotification_flagEnableStickyHun_fsiPermissionSoftDenied_showStickyHun() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true, /* isSticky= */ true); } @Test public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false, /* isSticky= */ false); } @@ -11530,39 +11586,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_GRANTED, true, /* isSticky= */ false); } @Test - public void testFixNotification_flagForceStickyHun_fsiPermissionHardDenied_showStickyHun() - throws Exception { - - verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true, - /* isSticky= */ true); - } - - @Test - public void testFixNotification_flagForceStickyHun_fsiPermissionSoftDenied_showStickyHun() - throws Exception { - - verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true, - /* isSticky= */ true); - } - - @Test - public void testFixNotification_flagForceStickyHun_fsiPermissionGranted_showStickyHun() - throws Exception { - - verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true, - /* isSticky= */ true); - } - - @Test public void fixNotification_withFgsFlag_butIsNotFgs() throws Exception { final ApplicationInfo applicationInfo = new ApplicationInfo(); when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java index 5147a08b5216..29848d0139b1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java @@ -171,19 +171,8 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { } @Test - public void testGetFsiState_stickyHunFlagDisabled_zero() { - final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ false, - /* hasFullScreenIntent= */ true, - /* hasFsiRequestedButDeniedFlag= */ true, - /* eventType= */ NOTIFICATION_POSTED); - assertEquals(0, fsiState); - } - - @Test public void testGetFsiState_isUpdate_zero() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ true, /* hasFsiRequestedButDeniedFlag= */ true, /* eventType= */ NOTIFICATION_UPDATED); @@ -193,7 +182,6 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { @Test public void testGetFsiState_hasFsi_allowedEnum() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ true, /* hasFsiRequestedButDeniedFlag= */ false, /* eventType= */ NOTIFICATION_POSTED); @@ -203,7 +191,6 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { @Test public void testGetFsiState_fsiPermissionDenied_deniedEnum() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ false, /* hasFsiRequestedButDeniedFlag= */ true, /* eventType= */ NOTIFICATION_POSTED); @@ -213,7 +200,6 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { @Test public void testGetFsiState_noFsi_noFsiEnum() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ false, /* hasFsiRequestedButDeniedFlag= */ false, /* eventType= */ NOTIFICATION_POSTED); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index c156e376ce3e..fe21103096ca 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -5733,17 +5733,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testGetFsiState_flagDisabled_zero() { - final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission */ true, /* isFlagEnabled= */ false); - - assertEquals(0, fsiState); - } - - @Test public void testGetFsiState_appDidNotRequest_enumNotRequested() { final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission */ false, /* isFlagEnabled= */ true); + /* requestedFsiPermission */ false); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState); } @@ -5754,7 +5746,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .thenReturn(PermissionManager.PERMISSION_GRANTED); final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true); + /* requestedFsiPermission= */ true); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState); } @@ -5765,7 +5757,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .thenReturn(PermissionManager.PERMISSION_SOFT_DENIED); final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true); + /* requestedFsiPermission = */ true); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState); } @@ -5776,7 +5768,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .thenReturn(PermissionManager.PERMISSION_HARD_DENIED); final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true); + /* requestedFsiPermission = */ true); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState); } @@ -5785,18 +5777,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testIsFsiPermissionUserSet_appDidNotRequest_false() { final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, - /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ true); - - assertFalse(isUserSet); - } - - @Test - public void testIsFsiPermissionUserSet_flagDisabled_false() { - final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, - /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, - /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ false); + /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET); assertFalse(isUserSet); } @@ -5805,8 +5786,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testIsFsiPermissionUserSet_userSet_true() { final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, - /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ true); + /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET); assertTrue(isUserSet); } @@ -5815,8 +5795,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testIsFsiPermissionUserSet_notUserSet_false() { final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, - /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ true); + /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET); assertFalse(isUserSet); } diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index 1b8d746f271f..e83f03d155aa 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -99,4 +99,7 @@ android_test { enabled: false, }, + data: [ + ":OverlayTestApp", + ], } diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 762e23c8e288..f2a1fe859634 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -119,6 +119,9 @@ </intent-filter> </activity> + <activity android:name="com.android.server.wm.ActivityRecordInputSinkTests$TestActivity" + android:exported="true"> + </activity> </application> <instrumentation diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml index 2717ef901216..f8ebeaddcb7e 100644 --- a/services/tests/wmtests/AndroidTest.xml +++ b/services/tests/wmtests/AndroidTest.xml @@ -21,6 +21,7 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="WmTests.apk" /> + <option name="test-file-name" value="OverlayTestApp.apk" /> </target_preparer> <option name="test-tag" value="WmTests" /> diff --git a/services/tests/wmtests/OverlayApp/Android.bp b/services/tests/wmtests/OverlayApp/Android.bp new file mode 100644 index 000000000000..77d5b2295dc9 --- /dev/null +++ b/services/tests/wmtests/OverlayApp/Android.bp @@ -0,0 +1,19 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "OverlayTestApp", + + srcs: ["**/*.java"], + + resource_dirs: ["res"], + + certificate: "platform", + platform_apis: true, +} diff --git a/services/tests/wmtests/OverlayApp/AndroidManifest.xml b/services/tests/wmtests/OverlayApp/AndroidManifest.xml new file mode 100644 index 000000000000..5b4ef577112a --- /dev/null +++ b/services/tests/wmtests/OverlayApp/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.overlay_app"> + <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" /> + + <application> + <activity android:name=".OverlayApp" + android:exported="true" + android:theme="@style/TranslucentFloatingTheme"> + </activity> + </application> +</manifest> diff --git a/services/tests/wmtests/OverlayApp/res/values/styles.xml b/services/tests/wmtests/OverlayApp/res/values/styles.xml new file mode 100644 index 000000000000..fff10a3f29ac --- /dev/null +++ b/services/tests/wmtests/OverlayApp/res/values/styles.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <style name="TranslucentFloatingTheme" > + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowIsFloating">true</item> + <item name="android:windowNoTitle">true</item> + + <!-- Disables starting window. --> + <item name="android:windowDisablePreview">true</item> + </style> +</resources> diff --git a/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java new file mode 100644 index 000000000000..89161c51dc53 --- /dev/null +++ b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.overlay_app; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; +import android.view.Gravity; +import android.view.WindowManager; +import android.widget.LinearLayout; + +/** + * Test app that is translucent not touchable modal. + * If launched with "disableInputSink" extra boolean value, this activity disables + * ActivityRecordInputSinkEnabled as long as the permission is granted. + */ +public class OverlayApp extends Activity { + private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout tv = new LinearLayout(this); + tv.setBackgroundColor(Color.GREEN); + tv.setPadding(50, 50, 50, 50); + tv.setGravity(Gravity.CENTER); + setContentView(tv); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + + if (getIntent().getBooleanExtra(KEY_DISABLE_INPUT_SINK, false)) { + setActivityRecordInputSinkEnabled(false); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java new file mode 100644 index 000000000000..3b280d9d45bd --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java @@ -0,0 +1,209 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.UiAutomation; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.MotionEvent; +import android.view.ViewGroup; +import android.widget.Button; +import android.window.WindowInfosListenerForTest; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.window.flags.Flags; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Internal variant of {@link android.server.wm.window.ActivityRecordInputSinkTests}. + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class ActivityRecordInputSinkTests { + private static final String OVERLAY_APP_PKG = "com.android.server.wm.overlay_app"; + private static final String OVERLAY_ACTIVITY = OVERLAY_APP_PKG + "/.OverlayApp"; + private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink"; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Rule + public final ActivityScenarioRule<TestActivity> mActivityRule = + new ActivityScenarioRule<>(TestActivity.class); + + private UiAutomation mUiAutomation; + + @Before + public void setUp() { + mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + } + + @After + public void tearDown() { + ActivityManager am = + InstrumentationRegistry.getInstrumentation().getContext().getSystemService( + ActivityManager.class); + mUiAutomation.adoptShellPermissionIdentity(); + try { + am.forceStopPackage(OVERLAY_APP_PKG); + } finally { + mUiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + public void testSimpleButtonPress() { + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(1, a.mNumClicked); + }); + } + + @Test + public void testSimpleButtonPress_withOverlay() throws InterruptedException { + startOverlayApp(false); + waitForOverlayApp(); + + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(0, a.mNumClicked); + }); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK) + public void testSimpleButtonPress_withOverlayDisableInputSink() throws InterruptedException { + startOverlayApp(true); + waitForOverlayApp(); + + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(1, a.mNumClicked); + }); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK) + public void testSimpleButtonPress_withOverlayDisableInputSink_flagDisabled() + throws InterruptedException { + startOverlayApp(true); + waitForOverlayApp(); + + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(0, a.mNumClicked); + }); + } + + private void startOverlayApp(boolean disableInputSink) { + String launchCommand = "am start -n " + OVERLAY_ACTIVITY; + if (disableInputSink) { + launchCommand += " --ez " + KEY_DISABLE_INPUT_SINK + " true"; + } + + mUiAutomation.adoptShellPermissionIdentity(); + try { + mUiAutomation.executeShellCommand(launchCommand); + } finally { + mUiAutomation.dropShellPermissionIdentity(); + } + } + + private void waitForOverlayApp() throws InterruptedException { + final var listenerHost = new WindowInfosListenerForTest(); + final var latch = new CountDownLatch(1); + final Consumer<List<WindowInfosListenerForTest.WindowInfo>> listener = windowInfos -> { + final boolean inputSinkReady = windowInfos.stream().anyMatch(info -> + info.isVisible + && info.name.contains("ActivityRecordInputSink " + OVERLAY_ACTIVITY)); + if (inputSinkReady) { + latch.countDown(); + } + }; + + listenerHost.addWindowInfosListener(listener); + try { + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } finally { + listenerHost.removeWindowInfosListener(listener); + } + } + + private void injectTapOnButton() { + Rect buttonBounds = new Rect(); + mActivityRule.getScenario().onActivity(a -> { + a.mButton.getBoundsOnScreen(buttonBounds); + }); + final int x = buttonBounds.centerX(); + final int y = buttonBounds.centerY(); + + MotionEvent down = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0); + mUiAutomation.injectInputEvent(down, true); + + SystemClock.sleep(10); + + MotionEvent up = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, x, y, 0); + mUiAutomation.injectInputEvent(up, true); + + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + public static class TestActivity extends Activity { + int mNumClicked = 0; + Button mButton; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mButton = new Button(this); + mButton.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + setContentView(mButton); + mButton.setOnClickListener(v -> mNumClicked++); + } + } +} diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp index 15aaa463cce7..83ced2c2258f 100644 --- a/tests/InputScreenshotTest/Android.bp +++ b/tests/InputScreenshotTest/Android.bp @@ -7,12 +7,27 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +filegroup { + name: "InputScreenshotTestRNGFiles", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + exclude_srcs: [ + "src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt", + "src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt", + ], +} + android_test { name: "InputScreenshotTests", srcs: [ "src/**/*.java", "src/**/*.kt", ], + exclude_srcs: [ + "src/android/input/screenshot/package-info.java", + ], platform_apis: true, certificate: "platform", static_libs: [ @@ -43,6 +58,7 @@ android_test { "hamcrest-library", "kotlin-test", "flag-junit", + "platform-parametric-runner-lib", "platform-test-annotations", "services.core.unboosted", "testables", diff --git a/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp new file mode 100644 index 000000000000..912f4b8069b4 --- /dev/null +++ b/tests/InputScreenshotTest/robotests/Android.bp @@ -0,0 +1,71 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_library { + name: "InputRoboRNGTestsAssetsLib", + asset_dirs: ["assets"], + sdk_version: "current", + platform_apis: true, + manifest: "AndroidManifest.xml", + optimize: { + enabled: false, + }, + use_resource_processor: true, +} + +android_app { + name: "InputRoboApp", + srcs: [], + static_libs: [ + "androidx.test.espresso.core", + "androidx.appcompat_appcompat", + "flag-junit", + "guava", + "InputRoboRNGTestsAssetsLib", + "platform-screenshot-diff-core", + "PlatformComposeSceneTransitionLayoutTestsUtils", + ], + manifest: "robo-manifest.xml", + aaptflags: [ + "--extra-packages", + "com.android.input.screenshot", + ], + dont_merge_manifests: true, + platform_apis: true, + system_ext_specific: true, + certificate: "platform", + privileged: true, + resource_dirs: [], + kotlincflags: ["-Xjvm-default=all"], + + plugins: ["dagger2-compiler"], + use_resource_processor: true, +} + +android_robolectric_test { + name: "InputRoboRNGTests", + srcs: [ + ":InputScreenshotTestRNGFiles", + ":flag-junit", + ":platform-test-screenshot-rules", + ], + // Do not add any new libraries here, they should be added to SystemUIGoogleRobo above. + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.test.uiautomator_uiautomator", + "androidx.test.ext.junit", + "inline-mockito-robolectric-prebuilt", + "platform-parametric-runner-lib", + "uiautomator-helpers", + ], + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + "truth", + ], + upstream: true, + java_resource_dirs: ["config"], + instrumentation_for: "InputRoboApp", +} diff --git a/tests/InputScreenshotTest/robotests/AndroidManifest.xml b/tests/InputScreenshotTest/robotests/AndroidManifest.xml new file mode 100644 index 000000000000..56893113288d --- /dev/null +++ b/tests/InputScreenshotTest/robotests/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.input.screenshot"> + <uses-sdk android:minSdkVersion="21"/> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> +</manifest> diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png Binary files differnew file mode 100644 index 000000000000..baf204a6cfb3 --- /dev/null +++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png Binary files differnew file mode 100644 index 000000000000..deb3ceeca7fb --- /dev/null +++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png Binary files differnew file mode 100644 index 000000000000..34e25f73d953 --- /dev/null +++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png diff --git a/tests/InputScreenshotTest/robotests/config/robolectric.properties b/tests/InputScreenshotTest/robotests/config/robolectric.properties new file mode 100644 index 000000000000..83d7549551ce --- /dev/null +++ b/tests/InputScreenshotTest/robotests/config/robolectric.properties @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +sdk=NEWEST_SDK
\ No newline at end of file diff --git a/tests/InputScreenshotTest/robotests/robo-manifest.xml b/tests/InputScreenshotTest/robotests/robo-manifest.xml new file mode 100644 index 000000000000..e86f58ef0e55 --- /dev/null +++ b/tests/InputScreenshotTest/robotests/robo-manifest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.input.screenshot" + coreApp="true"> + <application> + <activity + android:name="androidx.activity.ComponentActivity" + android:exported="true"> + </activity> + </application> +</manifest> diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt index c2c3d5530a00..75dab41d3609 100644 --- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt +++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt @@ -18,6 +18,7 @@ package com.android.input.screenshot import android.content.Context import android.graphics.Bitmap +import android.os.Build import androidx.activity.ComponentActivity import androidx.compose.foundation.Image import androidx.compose.ui.platform.ViewRootForTest @@ -49,15 +50,17 @@ class InputScreenshotTestRule( ) ) private val composeRule = createAndroidComposeRule<ComponentActivity>() - private val delegateRule = - RuleChain.outerRule(colorsRule) - .around(deviceEmulationRule) + private val roboRule = + RuleChain.outerRule(deviceEmulationRule) .around(screenshotRule) .around(composeRule) + private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule) private val matcher = UnitTestBitmapMatcher + private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false override fun apply(base: Statement, description: Description): Statement { - return delegateRule.apply(base, description) + val ruleToApply = if (isRobolectric) roboRule else delegateRule + return ruleToApply.apply(base, description) } /** @@ -84,4 +87,4 @@ class InputScreenshotTestRule( val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) } -}
\ No newline at end of file +} diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt index 8ae6dfd8b63b..ab7bb4eda899 100644 --- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt +++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt @@ -26,14 +26,15 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec /** A screenshot test for Keyboard layout preview for Iso physical layout. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } @@ -55,4 +56,4 @@ class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) } } -}
\ No newline at end of file +} diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java new file mode 100644 index 000000000000..4b5a56d3bd1d --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java @@ -0,0 +1,4 @@ +@GraphicsMode(GraphicsMode.Mode.NATIVE) +package com.android.input.screenshot; + +import org.robolectric.annotation.GraphicsMode; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java index 4a3a79803b65..668c94c0f91c 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java @@ -27,9 +27,10 @@ import java.util.concurrent.atomic.AtomicLong; /** * Tentative, partial implementation of the Parcel native methods, using Java's - * {@link ByteBuffer}. It turned out there's enough semantics differences between Parcel - * and {@link ByteBuffer}, so it didn't actually work. - * (e.g. Parcel seems to allow moving the data position to be beyond its size? Which + * {@code byte[]}. + * (We don't use a {@link ByteBuffer} because there's enough semantics differences between Parcel + * and {@link ByteBuffer}, and it didn't work out. + * e.g. Parcel seems to allow moving the data position to be beyond its size? Which * {@link ByteBuffer} wouldn't allow...) */ public class Parcel_host { diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh index 89daa2084420..85038be80c51 100755 --- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh +++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# This command is expected to be executed with: atest hoststubgen-invoke-test + set -e # Exit when any command files # This script runs HostStubGen directly with various arguments and make sure @@ -35,6 +37,12 @@ if [[ "$TEMP" == "" ]] ; then mkdir -p $TEMP fi +cleanup_temp() { + rm -fr $TEMP/* +} + +cleanup_temp + JAR=hoststubgen-test-tiny-framework.jar STUB=$TEMP/stub.jar IMPL=$TEMP/impl.jar @@ -47,12 +55,10 @@ HOSTSTUBGEN_OUT=$TEMP/output.txt # HostStubGen result in it. HOSTSTUBGEN_RC=0 -# Define the functions to - - # Note, because the build rule will only install hoststubgen.jar, but not the wrapper script, # we need to execute it manually with the java command. hoststubgen() { + echo "Running hoststubgen with: $*" java -jar ./hoststubgen.jar "$@" } @@ -62,7 +68,7 @@ run_hoststubgen() { echo "# Test: $test_name" - rm -f $HOSTSTUBGEN_OUT + cleanup_temp local filter_arg="" @@ -73,11 +79,21 @@ run_hoststubgen() { cat $ANNOTATION_FILTER fi + local stub_arg="" + local impl_arg="" + + if [[ "$STUB" != "" ]] ; then + stub_arg="--out-stub-jar $STUB" + fi + if [[ "$IMPL" != "" ]] ; then + impl_arg="--out-impl-jar $IMPL" + fi + hoststubgen \ --debug \ --in-jar $JAR \ - --out-stub-jar $STUB \ - --out-impl-jar $IMPL \ + $stub_arg \ + $impl_arg \ --stub-annotation \ android.hosttest.annotation.HostSideTestStub \ --keep-annotation \ @@ -105,6 +121,21 @@ run_hoststubgen() { return 0 } +assert_file_generated() { + local file="$1" + if [[ "$file" == "" ]] ; then + if [[ -f "$file" ]] ; then + echo "HostStubGen shouldn't have generated $file" + return 1 + fi + else + if ! [[ -f "$file" ]] ; then + echo "HostStubGen didn't generate $file" + return 1 + fi + fi +} + run_hoststubgen_for_success() { run_hoststubgen "$@" @@ -112,6 +143,9 @@ run_hoststubgen_for_success() { echo "HostStubGen expected to finish successfully, but failed with $rc" return 1 fi + + assert_file_generated "$STUB" + assert_file_generated "$IMPL" } run_hoststubgen_for_failure() { @@ -189,6 +223,11 @@ run_hoststubgen_for_success "One specific class disallowed, but it doesn't use a * # All other classes allowed " +STUB="" run_hoststubgen_for_success "No stub generation" "" + +IMPL="" run_hoststubgen_for_success "No impl generation" "" + +STUB="" IMPL="" run_hoststubgen_for_success "No stub, no impl generation" "" echo "All tests passed" diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 07bd2dc7c867..3cdddc23b332 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -237,8 +237,8 @@ class HostStubGen(val options: HostStubGenOptions) { */ private fun convert( inJar: String, - outStubJar: String, - outImplJar: String, + outStubJar: String?, + outImplJar: String?, filter: OutputFilter, enableChecker: Boolean, classes: ClassNodes, @@ -254,8 +254,8 @@ class HostStubGen(val options: HostStubGenOptions) { log.withIndent { // Open the input jar file and process each entry. ZipFile(inJar).use { inZip -> - ZipOutputStream(FileOutputStream(outStubJar)).use { stubOutStream -> - ZipOutputStream(FileOutputStream(outImplJar)).use { implOutStream -> + maybeWithZipOutputStream(outStubJar) { stubOutStream -> + maybeWithZipOutputStream(outImplJar) { implOutStream -> val inEntries = inZip.entries() while (inEntries.hasMoreElements()) { val entry = inEntries.nextElement() @@ -265,22 +265,29 @@ class HostStubGen(val options: HostStubGenOptions) { log.i("Converted all entries.") } } - log.i("Created stub: $outStubJar") - log.i("Created impl: $outImplJar") + outStubJar?.let { log.i("Created stub: $it") } + outImplJar?.let { log.i("Created impl: $it") } } } val end = System.currentTimeMillis() log.v("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0) } + private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T { + if (filename == null) { + return block(null) + } + return ZipOutputStream(FileOutputStream(filename)).use(block) + } + /** * Convert a single ZIP entry, which may or may not be a class file. */ private fun convertSingleEntry( inZip: ZipFile, entry: ZipEntry, - stubOutStream: ZipOutputStream, - implOutStream: ZipOutputStream, + stubOutStream: ZipOutputStream?, + implOutStream: ZipOutputStream?, filter: OutputFilter, packageRedirector: PackageRedirectRemapper, enableChecker: Boolean, @@ -316,8 +323,8 @@ class HostStubGen(val options: HostStubGenOptions) { // Unknown type, we just copy it to both output zip files. // TODO: We probably shouldn't do it for stub jar? log.v("Copying: %s", entry.name) - copyZipEntry(inZip, entry, stubOutStream) - copyZipEntry(inZip, entry, implOutStream) + stubOutStream?.let { copyZipEntry(inZip, entry, it) } + implOutStream?.let { copyZipEntry(inZip, entry, it) } } } @@ -346,8 +353,8 @@ class HostStubGen(val options: HostStubGenOptions) { private fun processSingleClass( inZip: ZipFile, entry: ZipEntry, - stubOutStream: ZipOutputStream, - implOutStream: ZipOutputStream, + stubOutStream: ZipOutputStream?, + implOutStream: ZipOutputStream?, filter: OutputFilter, packageRedirector: PackageRedirectRemapper, enableChecker: Boolean, @@ -361,7 +368,7 @@ class HostStubGen(val options: HostStubGenOptions) { return } // Generate stub first. - if (classPolicy.policy.needsInStub) { + if (stubOutStream != null && classPolicy.policy.needsInStub) { log.v("Creating stub class: %s Policy: %s", classInternalName, classPolicy) log.withIndent { BufferedInputStream(inZip.getInputStream(entry)).use { bis -> @@ -374,8 +381,8 @@ class HostStubGen(val options: HostStubGenOptions) { } } } - log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy) - if (classPolicy.policy.needsInImpl) { + if (implOutStream != null && classPolicy.policy.needsInImpl) { + log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy) log.withIndent { BufferedInputStream(inZip.getInputStream(entry)).use { bis -> val newEntry = ZipEntry(entry.name) diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index da5348707528..83f873d38f1b 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -28,10 +28,10 @@ class HostStubGenOptions( var inJar: String = "", /** Output stub jar file */ - var outStubJar: String = "", + var outStubJar: String? = null, /** Output implementation jar file */ - var outImplJar: String = "", + var outImplJar: String? = null, var inputJarDumpFile: String? = null, @@ -71,7 +71,7 @@ class HostStubGenOptions( var enablePreTrace: Boolean = false, var enablePostTrace: Boolean = false, - var enableNonStubMethodCallDetection: Boolean = true, + var enableNonStubMethodCallDetection: Boolean = false, ) { companion object { @@ -209,11 +209,14 @@ class HostStubGenOptions( if (ret.inJar.isEmpty()) { throw ArgumentsException("Required option missing: --in-jar") } - if (ret.outStubJar.isEmpty()) { - throw ArgumentsException("Required option missing: --out-stub-jar") + if (ret.outStubJar == null && ret.outImplJar == null) { + log.w("Neither --out-stub-jar nor --out-impl-jar is set." + + " $COMMAND_NAME will not generate jar files.") } - if (ret.outImplJar.isEmpty()) { - throw ArgumentsException("Required option missing: --out-impl-jar") + + if (ret.enableNonStubMethodCallDetection) { + log.w("--enable-non-stub-method-check is not fully implemented yet." + + " See the todo in doesMethodNeedNonStubCallCheck().") } return ret diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md index 20e2f873b152..f616ad61d219 100644 --- a/tools/hoststubgen/hoststubgen/test-framework/README.md +++ b/tools/hoststubgen/hoststubgen/test-framework/README.md @@ -14,12 +14,6 @@ tests. $ atest --no-bazel-mode HostStubGenTest-framework-test-host-test ``` -- With `run-ravenwood-test` - -``` -$ run-ravenwood-test HostStubGenTest-framework-test-host-test -``` - - Advanced option: `run-test-without-atest.sh` runs the test without using `atest` or `run-ravenwood-test` ``` diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md index f3c0450d42a3..3bfad9bd673b 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md @@ -13,12 +13,6 @@ This test doesn't use the actual android framework code. $ atest hoststubgen-test-tiny-test ``` -- With `run-ravenwood-test` should work too. This is the proper way to run it. - -``` -$ run-ravenwood-test hoststubgen-test-tiny-test -``` - - `run-test-manually.sh` also run the test, but it builds the stub/impl jars and the test without using the build system. This is useful for debugging the tool. diff --git a/tools/hoststubgen/scripts/Android.bp b/tools/hoststubgen/scripts/Android.bp index 5da805e5640e..b1ba07ec540d 100644 --- a/tools/hoststubgen/scripts/Android.bp +++ b/tools/hoststubgen/scripts/Android.bp @@ -18,9 +18,3 @@ genrule_defaults { tools: ["dump-jar"], cmd: "$(location dump-jar) -s -o $(out) $(in)", } - -sh_binary_host { - name: "run-ravenwood-test", - src: "run-ravenwood-test", - visibility: ["//visibility:public"], -} diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 2dac08969d44..82faa91e2cac 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -22,10 +22,10 @@ cd .. READY_TEST_MODULES=( HostStubGenTest-framework-all-test-host-test hoststubgen-test-tiny-test + CtsUtilTestCasesRavenwood ) MUST_BUILD_MODULES=( - run-ravenwood-test "${NOT_READY_TEST_MODULES[*]}" HostStubGenTest-framework-test ) @@ -51,8 +51,6 @@ run ./scripts/build-framework-hostside-jars-and-extract.sh # run ./scripts/build-framework-hostside-jars-without-genrules.sh # These tests should all pass. -run-ravenwood-test ${READY_TEST_MODULES[*]} - -run atest CtsUtilTestCasesRavenwood +run atest ${READY_TEST_MODULES[*]} echo ""${0##*/}" finished, with no unexpected failures. Ready to submit!"
\ No newline at end of file diff --git a/tools/hoststubgen/scripts/run-ravenwood-test b/tools/hoststubgen/scripts/run-ravenwood-test deleted file mode 100755 index 9bbb859f5c3d..000000000000 --- a/tools/hoststubgen/scripts/run-ravenwood-test +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e - -# Script to run a "Ravenwood" host side test. -# -# A proper way to run these tests is to use `atest`, but `atest` has a known issue of loading -# unrelated jar files as the class path, so for now we use this script to run host side tests. - -# Copy (with some changes) some functions from ../common.sh, so this script can be used without it. - -m() { - if (( $SKIP_BUILD )) ; then - echo "Skipping build: $*" 1>&2 - return 0 - fi - run ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@" -} - -run() { - echo "Running: $*" 1>&2 - "$@" -} - -run_junit_test_jar() { - local jar="$1" - echo "Starting test: $jar ..." - run cd "${jar%/*}" - - run ${JAVA:-java} $JAVA_OPTS \ - -cp $jar \ - org.junit.runner.JUnitCore \ - com.android.hoststubgen.hosthelper.HostTestSuite || return 1 - return 0 -} - -help() { - cat <<'EOF' - - run-ravenwood-test -- Run Ravenwood host tests - - Usage: - run-ravenwood-test [options] MODULE-NAME ... - - Options: - -h: Help - -t: Run test only, without building - -b: Build only, without running - - Example: - run-ravenwood-test HostStubGenTest-framework-test-host-test - -EOF -} - -#------------------------------------------------------------------------- -# Parse options -#------------------------------------------------------------------------- -build=0 -test=0 - -while getopts "htb" opt; do - case "$opt" in - h) help; exit 0 ;; - t) - test=1 - ;; - b) - build=1 - ;; - esac -done -shift $(($OPTIND - 1)) - -# If neither -t nor -b is provided, then build and run./ -if (( ( $build + $test ) == 0 )) ; then - build=1 - test=1 -fi - - -modules=("${@}") - -if (( "${#modules[@]}" == 0 )); then - help - exit 1 -fi - -#------------------------------------------------------------------------- -# Build -#------------------------------------------------------------------------- -if (( $build )) ; then - run m "${modules[@]}" -fi - -#------------------------------------------------------------------------- -# Run -#------------------------------------------------------------------------- - -failures=0 -if (( test )) ; then - for module in "${modules[@]}"; do - if run_junit_test_jar "$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/${module}/${module}.jar"; then - : # passed. - else - failures=$(( failures + 1 )) - fi - done - - if (( $failures > 0 )) ; then - echo "$failures test jar(s) failed." 1>&2 - exit 2 - fi -fi - -exit 0
\ No newline at end of file |