Merge "Add setDeviceVolume to VolumeControlProfile" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ce3e985..b54022b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -64,6 +64,7 @@
":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 @@
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 03a23ba..ca73378 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -32,7 +32,6 @@
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 @@
],
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 c42c7ca..2ace602 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 @@
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 @@
final SurfaceControl mOutSurfaceControl;
final IntSupplier mViewVisibility;
-
+ int mRelayoutSeq;
int mFlags;
RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -152,10 +153,11 @@
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 948e64f..9ffb704 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -25,7 +25,7 @@
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 be76c8a..812d4cd6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -36760,6 +36760,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
+ field @FlaggedApi("android.view.accessibility.collection_info_item_counts") 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);
+ @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 32d252e..275fe77 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3868,8 +3868,9 @@
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 @@
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 bb335fa..b5f7f23 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -20,6 +20,7 @@
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 @@
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 b35e87b..b8bd030 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 @@
}
}
+ @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 a3c5e1c..7370fc3 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -191,4 +191,14 @@
*/
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 56d0d1f..6c9c14f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -51,6 +51,7 @@
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 @@
* <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 cc56a1c..d8448dc 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -72,6 +72,7 @@
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 bbd07b8..35ce102 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -48,3 +48,10 @@
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 49a4467..47a152a 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 c050a55..9517fb9 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 46656d1..fd41b86 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 a382e09..0000000
--- 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 59ed045..1f25fd0 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 @@
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 @@
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 e9a2aaa..4f0bfc7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -387,6 +387,24 @@
"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 @@
@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 @@
* 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 @@
* <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 @@
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 @@
}
}
+ /**
+ * 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 int developmentInstallFlags = 0;
/** {@hide} */
public int unarchiveId = -1;
+ /** {@hide} */
+ public IntentSender unarchiveIntentSender;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2618,6 +2771,7 @@
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
unarchiveId = source.readInt();
+ unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
}
/** {@hide} */
@@ -2652,6 +2806,7 @@
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
ret.unarchiveId = unarchiveId;
+ ret.unarchiveIntentSender = unarchiveIntentSender;
return ret;
}
@@ -3364,6 +3519,7 @@
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.printPair("unarchiveId", unarchiveId);
+ pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
pw.println();
}
@@ -3408,6 +3564,7 @@
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 f817fb8..1100731 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -82,8 +82,8 @@
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 afacf1a..c68c0c9 100644
--- a/core/java/android/permission/IOnPermissionsChangeListener.aidl
+++ b/core/java/android/permission/IOnPermissionsChangeListener.aidl
@@ -21,5 +21,5 @@
* {@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 7a158c5..91adc37 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1738,8 +1738,9 @@
}
@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 @@
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 33c15d77..ff6ec29 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18,6 +18,7 @@
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.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 @@
= "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 bb0e6ab..cb0b5fa 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 21a8eab..e5910ea 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 43bfe13..53aed49 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,7 +23,7 @@
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 @@
* {@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 @@
* @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 @@
*
* @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 @@
* 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 @@
* 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 @@
*
* @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 @@
* @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 @@
* 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 @@
/**
* Creates a new Builder.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public Builder() {
}
@@ -6343,6 +6351,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setImportantForAccessibilityItemCount(
int importantForAccessibilityItemCount) {
mImportantForAccessibilityItemCount = importantForAccessibilityItemCount;
@@ -6411,6 +6425,7 @@
* 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 950fa4b..c337cb4 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 {
+ 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 {
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 11bd22f..0da03fb 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -39,4 +39,12 @@
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 e3bb1fe..7be27be 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 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 c7ab6aa..f5b877a 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 @@
&& 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 27dc870..b158f88 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 @@
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 @@
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 b63ee1b..a9d1a2a 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 @@
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 4533db6..3abdb6f 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 @@
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 ee45fbb..d4bca2a 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 @@
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 @@
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 2c15fc6..8412cba 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 @@
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 71c2ddc..9197554 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 @@
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 @@
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 @@
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 49012b0..a8a659e 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 @@
}
}
};
+
+ 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 f1b53ed..efed8c3 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -851,8 +851,6 @@
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 865f431..3b3bf8ca 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 @@
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 9ab9ba8..d78038e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -224,11 +224,6 @@
extra_check_modules: ["SystemUILintChecker"],
warning_checks: ["MissingApacheLicenseDetector"],
},
- errorprone: {
- javacflags: [
- "-Xep:InvalidPatternSyntax:WARN",
- ],
- },
}
filegroup {
@@ -553,11 +548,6 @@
test: true,
extra_check_modules: ["SystemUILintChecker"],
},
- errorprone: {
- javacflags: [
- "-Xep:InvalidPatternSyntax:WARN",
- ],
- },
}
android_app {
@@ -599,12 +589,6 @@
},
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 952f056..cc99f5e 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 355e75d..9c08f5e 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 99529a1..9a50d83 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.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 @@
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 58c4f0d..ef72967 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 @@
+ "\\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 8eda96f..64f61d9 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.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 @@
* 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 @@
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground);
mRippleColor = null;
mBackground.mutate();
if (mBackground != null) {
@@ -144,25 +148,46 @@
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[]{}},
+ mBackgroundDrawableToTint.setTint(tintColor);
+ mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP);
- new int[]{tintColor, tintColor, tintColor}
- );
- mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP);
- mBackground.setTintList(stateList);
- } else {
- mBackground.setTintList(null);
- }
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 61a79b7..6944453 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 @@
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 @@
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 @@
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 a7e7dd0..2b51ac5 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 @@
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 @@
@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 1c7fd56..7361f6b 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 @@
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 @@
@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 df6f0d7..d2c046c 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 @@
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 @@
@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 7babff5..2ac0cb7 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.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.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 4b1c7e8..4422764 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.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 @@
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 df7609c..200cfd3 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.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 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 b9c269c..71a1f01 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.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 @@
@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 @@
@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 7907d61..77b6d583 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1182,8 +1182,8 @@
// 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 @@
}
}
- 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 bae06347..0492f43 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 @@
}
}
+ // 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 @@
unhideNotificationsForPackages(pkgList, uidList);
}
}
-
mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList);
}
}
@@ -4021,11 +4036,8 @@
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 @@
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 @@
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 @@
}
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 @@
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 d2e980b..9a6ea2c 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 @@
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 @@
* @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 783e9bb..252664a 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 @@
* @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 @@
* 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 @@
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 79cd2a0..92d469c 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 @@
*/
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 11a6d1b..e5c4ccc 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 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 @@
}
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 @@
}
/**
- * @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 @@
}
/**
- * @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 @@
}
/**
- * @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 @@
}
/**
- * @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 @@
}
/**
- * @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 @@
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 @@
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 65c6329..3b3d79e 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 @@
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 eff6157..d2a4c27 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.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 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
+ verifyUninstallPermissions();
+
CompletableFuture<ArchiveState> archiveStateFuture;
try {
archiveStateFuture = createArchiveState(packageName, userId);
@@ -182,6 +185,7 @@
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 @@
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 @@
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 @@
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 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"unarchiveApp");
+ verifyInstallPermissions();
+
PackageStateInternal ps;
try {
ps = getPackageState(packageName, snapshot, binderUid, userId);
@@ -400,9 +420,12 @@
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 @@
() -> 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 af43a8b..c9663fc 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.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.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.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 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 @@
}
}
+ // 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 6f45d2b..f992bd8 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 @@
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 72090f2..b50d0a0 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 @@
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 4e14c90..8556317 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 @@
*/
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 ee46ce1..b3672ec 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 @@
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 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 43d62aa..cfdef14 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 @@
*
* @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 @@
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 @@
}
}
- void prepareWebViewInSystemServer() {
+ @Override
+ public void prepareWebViewInSystemServer() {
mSystemInterface.notifyZygote(isMultiProcessEnabled());
try {
synchronized (mLock) {
@@ -210,7 +212,8 @@
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 @@
handleUserChange();
}
- void handleUserRemoved(int userId) {
+ @Override
+ public void handleUserRemoved(int userId) {
handleUserChange();
}
@@ -232,14 +236,16 @@
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 @@
* 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 @@
/**
* 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 @@
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 @@
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 @@
}
}
- 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 @@
/**
* 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 0000000..e618c7e
--- /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 0000000..a9c3dc4
--- /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 0000000..1411acc
--- /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 faccca8..315e7d8 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.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 @@
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 081759d..d90d4ff 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 @@
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 be7d9b6..c61d863 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 @@
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 0d196b4..7c53950 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 @@
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 @@
override fun onDevicePermissionFlagsChanged(
appId: Int,
userId: Int,
- deviceId: String,
+ persistentDeviceId: String,
permissionName: String,
oldFlags: Int,
newFlags: Int
@@ -2681,7 +2681,8 @@
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 @@
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 @@
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 @@
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 a9f5b14..18a2acc 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 4b6183d..8bc027d 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 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 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 @@
@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 @@
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 7fb8b30..b45dcd4 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_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.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.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.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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 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 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 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 5147a08..29848d0 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 @@
}
@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 @@
@Test
public void testGetFsiState_hasFsi_allowedEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ true,
/* hasFsiRequestedButDeniedFlag= */ false,
/* eventType= */ NOTIFICATION_POSTED);
@@ -203,7 +191,6 @@
@Test
public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ false,
/* hasFsiRequestedButDeniedFlag= */ true,
/* eventType= */ NOTIFICATION_POSTED);
@@ -213,7 +200,6 @@
@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 c156e37..fe21103 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 @@
}
@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 @@
.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 @@
.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 @@
.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 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 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 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 1b8d746..e83f03d 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -99,4 +99,7 @@
enabled: false,
},
+ data: [
+ ":OverlayTestApp",
+ ],
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 762e23c..f2a1fe8 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 2717ef90..f8ebead 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 0000000..77d5b22
--- /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 0000000..5b4ef57
--- /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 0000000..fff10a3
--- /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 0000000..89161c5
--- /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 0000000..3b280d9
--- /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 15aaa46..83ced2c 100644
--- a/tests/InputScreenshotTest/Android.bp
+++ b/tests/InputScreenshotTest/Android.bp
@@ -7,12 +7,27 @@
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 @@
"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 0000000..912f4b80
--- /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 0000000..5689311
--- /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
new file mode 100644
index 0000000..baf204a
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
new file mode 100644
index 0000000..deb3cee
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
new file mode 100644
index 0000000..34e25f7
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/config/robolectric.properties b/tests/InputScreenshotTest/robotests/config/robolectric.properties
new file mode 100644
index 0000000..83d7549
--- /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 0000000..e86f58e
--- /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 c2c3d55..75dab41 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -18,6 +18,7 @@
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 @@
)
)
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 @@
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 8ae6dfd..ab7bb4e 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.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 @@
}
}
-}
\ 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 0000000..4b5a56d
--- /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 4a3a798..668c94c 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 @@
/**
* 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 89daa20..85038be 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 @@
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 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 @@
echo "# Test: $test_name"
- rm -f $HOSTSTUBGEN_OUT
+ cleanup_temp
local filter_arg=""
@@ -73,11 +79,21 @@
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 @@
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 @@
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 @@
* # 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 07bd2dc..3cdddc2 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 @@
*/
private fun convert(
inJar: String,
- outStubJar: String,
- outImplJar: String,
+ outStubJar: String?,
+ outImplJar: String?,
filter: OutputFilter,
enableChecker: Boolean,
classes: ClassNodes,
@@ -254,8 +254,8 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
}
}
}
- 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 da53487..83f873d 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 @@
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 @@
var enablePreTrace: Boolean = false,
var enablePostTrace: Boolean = false,
- var enableNonStubMethodCallDetection: Boolean = true,
+ var enableNonStubMethodCallDetection: Boolean = false,
) {
companion object {
@@ -209,11 +209,14 @@
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 20e2f87..f616ad6 100644
--- a/tools/hoststubgen/hoststubgen/test-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-framework/README.md
@@ -14,12 +14,6 @@
$ 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 f3c0450..3bfad9b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
@@ -13,12 +13,6 @@
$ 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 5da805e..b1ba07e 100644
--- a/tools/hoststubgen/scripts/Android.bp
+++ b/tools/hoststubgen/scripts/Android.bp
@@ -18,9 +18,3 @@
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 2dac089..82faa91 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -22,10 +22,10 @@
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-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 9bbb859..0000000
--- 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