Merge "Move counter producer off of vendor partition." 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/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 275fe77..c282e4b6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -307,7 +307,7 @@
field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
- field public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
+ field @FlaggedApi("com.android.net.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bb335fa..6c10f49 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;
@@ -7603,15 +7604,17 @@
* @param taskDescription The TaskDescription properties that describe the task with this activity
*/
public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
- if (mTaskDescription != taskDescription) {
- mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
- // Scale the icon down to something reasonable if it is provided
- if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
- final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
- final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
- true);
- mTaskDescription.setIcon(Icon.createWithBitmap(icon));
- }
+ if (taskDescription == null || mTaskDescription.equals(taskDescription)) {
+ return;
+ }
+
+ mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
+ // Scale the icon down to something reasonable if it is provided
+ if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
+ final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
+ final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
+ true);
+ mTaskDescription.setIcon(Icon.createWithBitmap(icon));
}
ActivityClient.getInstance().setTaskDescription(mToken, mTaskDescription);
}
@@ -9439,6 +9442,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/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/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 89f889f..fe95a2a 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1414,7 +1414,7 @@
* otherwise the value will be equal to 1.
* Note that this level is just a number of supported levels (the granularity of control).
* There is no actual physical power units tied to this level.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#FLASH_MODE
*/
@@ -1430,7 +1430,7 @@
* or equal to <code>android.flash.info.singleStrengthMaxLevel</code>.
* Note for devices that do not support the manual flash strength control
* feature, this level will always be equal to 1.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*/
@PublicKey
@NonNull
@@ -1450,7 +1450,7 @@
* android.flash.info.singleStrengthMaxLevel i.e. the ratio of
* android.flash.info.torchStrengthMaxLevel:android.flash.info.singleStrengthMaxLevel
* is not guaranteed to be the ratio of actual brightness.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#FLASH_MODE
*/
@@ -1466,7 +1466,7 @@
* or equal to android.flash.info.torchStrengthMaxLevel.
* Note for the devices that do not support the manual flash strength control feature,
* this level will always be equal to 1.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5d06978..93cae54 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2688,7 +2688,7 @@
* set to TORCH;
* <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to SINGLE</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#FLASH_MODE
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 0d204f3..12ab0f6 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2974,7 +2974,7 @@
* set to TORCH;
* <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to SINGLE</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#FLASH_MODE
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/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7b075e6..4d208c6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2301,6 +2301,7 @@
<!-- Allows system apps to call methods to register itself as a mDNS offload engine.
<p>Not for use by third-party or privileged applications.
@SystemApi
+ @FlaggedApi("com.android.net.flags.register_nsd_offload_engine")
@hide This should only be used by system apps.
-->
<permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE"
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/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index b528089d15..5c02dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -61,9 +62,10 @@
}
final int mode = change.getMode();
+ final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
- && TransitionUtil.isOpenOrCloseMode(mode)) {
- notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode));
+ && (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture)) {
+ notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode) || isBackGesture);
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index a5629c8..b355ab0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -14,8 +14,6 @@
import android.window.TransitionInfo
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import androidx.test.filters.SmallTest
-import com.android.server.testutils.any
-import com.android.server.testutils.mock
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -28,8 +26,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index ea7c0d9..421c445 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -18,8 +18,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -145,6 +147,26 @@
verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
}
+ @Test
+ public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ when(change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)).thenReturn(true);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_CHANGE);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(1)).onHomeVisibilityChanged(true);
+ }
+
+
/**
* Helper class to initialize variables for the rest.
*/
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index d056248..8748dab 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -603,7 +603,7 @@
std::unique_ptr<Asset> asset = assets->GetAssetsProvider()->Open(filename, mode);
if (asset) {
if (out_cookie != nullptr) {
- *out_cookie = i;
+ *out_cookie = i - 1;
}
return asset;
}
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/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index de842e6..9f63dfd 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1065,17 +1065,8 @@
* @see #isVolumeFixed()
*/
public void adjustVolume(int direction, @PublicVolumeFlags int flags) {
- if (autoPublicVolumeApiHardening()) {
- final IAudioService service = getService();
- try {
- service.adjustVolume(direction, flags);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
- helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
+ helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
}
/**
@@ -1104,17 +1095,8 @@
*/
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType,
@PublicVolumeFlags int flags) {
- if (autoPublicVolumeApiHardening()) {
- final IAudioService service = getService();
- try {
- service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
- helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
+ helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
}
/** @hide */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 11e3a08..b4ca485 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -500,10 +500,6 @@
in String packageName, int uid, int pid, in UserHandle userHandle,
int targetSdkVersion);
- oneway void adjustVolume(int direction, int flags);
-
- oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
-
boolean isMusicActive(in boolean remotely);
int getDeviceMaskForStream(in int streamType);
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/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
index 93a5082..071f9f4 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
@@ -116,6 +116,9 @@
base: 'w'
shift, capslock: 'W'
shift+capslock: 'w'
+ ralt: '\u1e83'
+ shift+ralt, capslock+ralt: '\u1e82'
+ shift+capslock+ralt: '\u1e83'
}
key E {
@@ -147,6 +150,9 @@
base: 'y'
shift, capslock: 'Y'
shift+capslock: 'y'
+ ralt: '\u00fd'
+ shift+ralt, capslock+ralt: '\u00dd'
+ shift+capslock+ralt: '\u00fd'
}
key U {
@@ -313,6 +319,9 @@
base: 'c'
shift, capslock: 'C'
shift+capslock: 'c'
+ ralt: '\u00e7'
+ shift+ralt, capslock+ralt: '\u00c7'
+ shift+capslock+ralt: '\u00e7'
}
key V {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 4906304..636f98d 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -44,7 +44,7 @@
label: '2'
base: '\u00e9'
shift: '2'
- ralt: '~'
+ ralt: '\u0303'
}
key 3 {
@@ -79,7 +79,7 @@
label: '7'
base: '\u00e8'
shift: '7'
- ralt: '`'
+ ralt: '\u0300'
}
key 8 {
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 7f23f74..ee49b23 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -94,7 +94,7 @@
android:name="keyboard_layout_swiss_german"
android:label="@string/keyboard_layout_swiss_german_label"
android:keyboardLayout="@raw/keyboard_layout_swiss_german"
- android:keyboardLocale="de-Latn-CH"
+ android:keyboardLocale="de-Latn-CH|gsw-Latn-CH"
android:keyboardLayoutType="qwertz" />
<keyboard-layout
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index f83e37b..3774b88 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -37,9 +37,7 @@
import java.util.List;
import java.util.concurrent.Executor;
-/**
- * VolumeControlProfile handles Bluetooth Volume Control Controller role
- */
+/** VolumeControlProfile handles Bluetooth Volume Control Controller role */
public class VolumeControlProfile implements LocalBluetoothProfile {
private static final String TAG = "VolumeControlProfile";
private static boolean DEBUG = true;
@@ -77,8 +75,8 @@
}
device = mDeviceManager.addDevice(nextDevice);
}
- device.onProfileStateChanged(VolumeControlProfile.this,
- BluetoothProfile.STATE_CONNECTED);
+ device.onProfileStateChanged(
+ VolumeControlProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
@@ -95,32 +93,36 @@
}
}
- VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ VolumeControlProfile(
+ Context context,
+ CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mContext = context;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
- new VolumeControlProfile.VolumeControlProfileServiceListener(),
- BluetoothProfile.VOLUME_CONTROL);
+ BluetoothAdapter.getDefaultAdapter()
+ .getProfileProxy(
+ context,
+ new VolumeControlProfile.VolumeControlProfileServiceListener(),
+ BluetoothProfile.VOLUME_CONTROL);
}
-
/**
- * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the
- * operation of this profile.
+ * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the operation
+ * of this profile.
*
- * Repeated registration of the same <var>callback</var> object will have no effect after
- * the first call to this method, even when the <var>executor</var> is different. API caller
- * would have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with
- * the same callback object before registering it again.
+ * <p>Repeated registration of the same <var>callback</var> object will have no effect after the
+ * first call to this method, even when the <var>executor</var> is different. API caller would
+ * have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with the same
+ * callback object before registering it again.
*
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
* @throws IllegalArgumentException if a null executor or callback is given
*/
- public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+ public void registerCallback(
+ @NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothVolumeControl.Callback callback) {
if (mService == null) {
Log.w(TAG, "Proxy not attached to service. Cannot register callback.");
@@ -131,8 +133,9 @@
/**
* Unregisters the specified {@link BluetoothVolumeControl.Callback}.
- * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling
- * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
+ *
+ * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling {@link
+ * #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
*
* <p>Callbacks are automatically unregistered when application process goes away
*
@@ -153,8 +156,8 @@
* @param device {@link BluetoothDevice} representing the remote device
* @param volumeOffset volume offset to be set on the remote device
*/
- public void setVolumeOffset(BluetoothDevice device,
- @IntRange(from = -255, to = 255) int volumeOffset) {
+ public void setVolumeOffset(
+ BluetoothDevice device, @IntRange(from = -255, to = 255) int volumeOffset) {
if (mService == null) {
Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
return;
@@ -165,16 +168,13 @@
}
mService.setVolumeOffset(device, volumeOffset);
}
-
/**
- * Provides information about the possibility to set volume offset on the remote device.
- * If the remote device supports Volume Offset Control Service, it is automatically
- * connected.
+ * Provides information about the possibility to set volume offset on the remote device. If the
+ * remote device supports Volume Offset Control Service, it is automatically connected.
*
* @param device {@link BluetoothDevice} representing the remote device
* @return {@code true} if volume offset function is supported and available to use on the
- * remote device. When Bluetooth is off, the return value should always be
- * {@code false}.
+ * remote device. When Bluetooth is off, the return value should always be {@code false}.
*/
public boolean isVolumeOffsetAvailable(BluetoothDevice device) {
if (mService == null) {
@@ -188,6 +188,28 @@
return mService.isVolumeOffsetAvailable(device);
}
+ /**
+ * Tells the remote device to set a volume.
+ *
+ * @param device {@link BluetoothDevice} representing the remote device
+ * @param volume volume to be set on the remote device
+ * @param isGroupOp whether to set the volume to remote devices within the same CSIP group
+ */
+ public void setDeviceVolume(
+ BluetoothDevice device,
+ @IntRange(from = 0, to = 255) int volume,
+ boolean isGroupOp) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
+ return;
+ }
+ if (device == null) {
+ Log.w(TAG, "Device is null. Cannot set volume offset.");
+ return;
+ }
+ mService.setDeviceVolume(device, volume, isGroupOp);
+ }
+
@Override
public boolean accessProfileEnabled() {
return false;
@@ -199,10 +221,9 @@
}
/**
- * Gets VolumeControlProfile devices matching connection states{
- * {@code BluetoothProfile.STATE_CONNECTED},
- * {@code BluetoothProfile.STATE_CONNECTING},
- * {@code BluetoothProfile.STATE_DISCONNECTING}}
+ * Gets VolumeControlProfile devices matching connection states{ {@code
+ * BluetoothProfile.STATE_CONNECTED}, {@code BluetoothProfile.STATE_CONNECTING}, {@code
+ * BluetoothProfile.STATE_DISCONNECTING}}
*
* @return Matching device list
*/
@@ -211,8 +232,11 @@
return new ArrayList<BluetoothDevice>(0);
}
return mService.getDevicesMatchingConnectionStates(
- new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING});
+ new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ });
}
@Override
@@ -285,7 +309,7 @@
@Override
public int getSummaryResourceForDevice(BluetoothDevice device) {
- return 0; // VCP profile not displayed in UI
+ return 0; // VCP profile not displayed in UI
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index c560627..fe1529d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -54,18 +54,14 @@
public class VolumeControlProfileTest {
private static final int TEST_VOLUME_OFFSET = 10;
+ private static final int TEST_VOLUME_VALUE = 10;
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
- @Mock
- private CachedBluetoothDeviceManager mDeviceManager;
- @Mock
- private LocalBluetoothProfileManager mProfileManager;
- @Mock
- private BluetoothDevice mBluetoothDevice;
- @Mock
- private BluetoothVolumeControl mService;
+ @Mock private CachedBluetoothDeviceManager mDeviceManager;
+ @Mock private LocalBluetoothProfileManager mProfileManager;
+ @Mock private BluetoothDevice mBluetoothDevice;
+ @Mock private BluetoothVolumeControl mService;
private final Context mContext = ApplicationProvider.getApplicationContext();
private BluetoothProfile.ServiceListener mServiceListener;
@@ -177,14 +173,14 @@
@Test
public void getConnectedDevices_returnCorrectList() {
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
- int[] connectedStates = new int[] {
- BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING};
- List<BluetoothDevice> connectedList = Arrays.asList(
- mBluetoothDevice,
- mBluetoothDevice,
- mBluetoothDevice);
+ int[] connectedStates =
+ new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ };
+ List<BluetoothDevice> connectedList =
+ Arrays.asList(mBluetoothDevice, mBluetoothDevice, mBluetoothDevice);
when(mService.getDevicesMatchingConnectionStates(connectedStates))
.thenReturn(connectedList);
@@ -222,6 +218,16 @@
}
@Test
+ public void setDeviceVolume_verifyIsCalled() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ mProfile.setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
+
+ verify(mService)
+ .setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
+ }
+
+ @Test
public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() {
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true);
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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 98fda3e..1710a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -707,7 +707,7 @@
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
- val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog")
+ val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
// TODO(b/300995746): Tracking Bug
/** A resource flag for whether the communal service is enabled. */
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/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d65a69c..9ee3d22 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -64,13 +64,13 @@
import com.android.internal.annotations.GuardedBy;
import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
@@ -99,6 +99,7 @@
@SysUISingleton
public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000;
private static final int DYNAMIC_STREAM_START_INDEX = 100;
@@ -1339,14 +1340,24 @@
private boolean showForSession(Token token) {
if (mVolumeAdjustmentForRemoteGroupSessions) {
+ if (DEBUG) {
+ Log.d(TAG, "Volume adjustment for remote group sessions allowed,"
+ + " showForSession: true");
+ }
return true;
}
MediaController ctr = new MediaController(mContext, token);
String packageName = ctr.getPackageName();
List<RoutingSessionInfo> sessions =
mRouter2Manager.getRoutingSessions(packageName);
-
+ if (DEBUG) {
+ Log.d(TAG, "Found " + sessions.size() + " routing sessions for package name "
+ + packageName);
+ }
for (RoutingSessionInfo session : sessions) {
+ if (DEBUG) {
+ Log.d(TAG, "Found routingSessionInfo: " + session);
+ }
if (!session.isSystemSession()
&& session.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED) {
return true;
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/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 5635dd5..42ab05f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -205,7 +206,10 @@
intent,
PendingIntent.FLAG_MUTABLE
| PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
- /* options= */ null, UserHandle.CURRENT);
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(), UserHandle.CURRENT);
if (sDebug) {
Slog.d(TAG, "startActivity add save UI restored with intent=" + intent);
}
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/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 215970e..e51ef29 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -864,6 +864,23 @@
}
@Override
+ public @NonNull Set<String> getAllPersistentDeviceIds() {
+ Set<String> persistentIds = new ArraySet<>();
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mActiveAssociations.size(); ++i) {
+ AssociationInfo associationInfo = mActiveAssociations.get(i);
+ if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(
+ associationInfo.getDeviceProfile())) {
+ persistentIds.add(
+ VirtualDeviceImpl.createPersistentDeviceId(
+ associationInfo.getId()));
+ }
+ }
+ }
+ return persistentIds;
+ }
+
+ @Override
public void registerAppsOnVirtualDeviceListener(
@NonNull AppsOnVirtualDeviceListener listener) {
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
index a570d09..6940ffe 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -69,6 +69,11 @@
}
@Override
+ public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException {
+ camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null);
+ }
+
+ @Override
public void onStreamClosed(int streamId) throws RemoteException {
camera.onStreamClosed(streamId);
}
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/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5b54561..e07631c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -193,6 +193,12 @@
private @Nullable BroadcastProcessQueue mRunningColdStart;
/**
+ * Indicates whether we have queued a message to check pending cold start validity.
+ */
+ @GuardedBy("mService")
+ private boolean mCheckPendingColdStartQueued;
+
+ /**
* Collection of latches waiting for device to reach specific state. The
* first argument is a function to test for the desired state, and the
* second argument is the latch to release once that state is reached.
@@ -302,7 +308,11 @@
return true;
}
case MSG_CHECK_PENDING_COLD_START_VALIDITY: {
- checkPendingColdStartValidity();
+ synchronized (mService) {
+ /* Clear this as we have just received the broadcast. */
+ mCheckPendingColdStartQueued = false;
+ checkPendingColdStartValidityLocked();
+ }
return true;
}
case MSG_PROCESS_FREEZABLE_CHANGED: {
@@ -549,7 +559,7 @@
mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
}
- checkPendingColdStartValidity();
+ checkPendingColdStartValidityLocked();
checkAndRemoveWaitingFor();
traceEnd(cookie);
@@ -573,22 +583,24 @@
enqueueUpdateRunningList();
}
- private void checkPendingColdStartValidity() {
+ @GuardedBy("mService")
+ private void checkPendingColdStartValidityLocked() {
// There are a few cases where a starting process gets killed but AMS doesn't report
// this event. So, once we start waiting for a pending cold start, periodically check
// if the pending start is still valid and if not, clear it so that the queue doesn't
// keep waiting for the process start forever.
- synchronized (mService) {
- // If there is no pending cold start, then nothing to do.
- if (mRunningColdStart == null) {
- return;
- }
- if (isPendingColdStartValid()) {
+ // If there is no pending cold start, then nothing to do.
+ if (mRunningColdStart == null) {
+ return;
+ }
+ if (isPendingColdStartValid()) {
+ if (!mCheckPendingColdStartQueued) {
mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_PENDING_COLD_START_VALIDITY,
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
- } else {
- clearInvalidPendingColdStart();
+ mCheckPendingColdStartQueued = true;
}
+ } else {
+ clearInvalidPendingColdStart();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1ef4333..df106a7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -154,7 +154,6 @@
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionManager;
-import android.media.session.MediaSessionManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -312,9 +311,6 @@
private final ContentResolver mContentResolver;
private final AppOpsManager mAppOps;
- /** do not use directly, use getMediaSessionManager() which handles lazy initialization */
- @Nullable private volatile MediaSessionManager mMediaSessionManager;
-
// the platform type affects volume and silent mode behavior
private final int mPlatformType;
@@ -945,8 +941,6 @@
private final SoundDoseHelper mSoundDoseHelper;
- private final HardeningEnforcer mHardeningEnforcer;
-
private final Object mSupportedSystemUsagesLock = new Object();
@GuardedBy("mSupportedSystemUsagesLock")
private @AttributeSystemUsage int[] mSupportedSystemUsages =
@@ -1321,8 +1315,6 @@
mDisplayManager = context.getSystemService(DisplayManager.class);
mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler);
-
- mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive());
}
private void initVolumeStreamStates() {
@@ -1394,6 +1386,7 @@
// check on volume initialization
checkVolumeRangeInitialization("AudioService()");
+
}
private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
@@ -1406,14 +1399,6 @@
}
};
- private MediaSessionManager getMediaSessionManager() {
- if (mMediaSessionManager == null) {
- mMediaSessionManager = (MediaSessionManager) mContext
- .getSystemService(Context.MEDIA_SESSION_SERVICE);
- }
- return mMediaSessionManager;
- }
-
/**
* Initialize intent receives and settings observers for this service.
* Must be called after createStreamStates() as the handling of some events
@@ -3442,10 +3427,6 @@
* Part of service interface, check permissions here */
public void adjustStreamVolumeWithAttribution(int streamType, int direction, int flags,
String callingPackage, String attributionTag) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME)) {
- return;
- }
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
+ "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
@@ -4222,10 +4203,6 @@
* Part of service interface, check permissions here */
public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
String callingPackage, String attributionTag) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME)) {
- return;
- }
setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null,
callingPackage, attributionTag);
}
@@ -5080,7 +5057,6 @@
/** @see AudioManager#setMasterMute(boolean, int) */
public void setMasterMute(boolean mute, int flags, String callingPackage, int userId,
String attributionTag) {
-
super.setMasterMute_enforcePermission();
setMasterMuteInternal(mute, flags, callingPackage,
@@ -5446,10 +5422,6 @@
}
public void setRingerModeExternal(int ringerMode, String caller) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_RINGER_MODE)) {
- return;
- }
if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode)
&& !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) {
throw new SecurityException("Not allowed to change Do Not Disturb state");
@@ -6202,35 +6174,6 @@
AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
}
- /**
- * @see AudioManager#adjustVolume(int, int)
- * This method is redirected from AudioManager to AudioService for API hardening rules
- * enforcement then to MediaSession for implementation.
- */
- @Override
- public void adjustVolume(int direction, int flags) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_VOLUME)) {
- return;
- }
- getMediaSessionManager().dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
- direction, flags);
- }
-
- /**
- * @see AudioManager#adjustSuggestedStreamVolume(int, int, int)
- * This method is redirected from AudioManager to AudioService for API hardening rules
- * enforcement then to MediaSession for implementation.
- */
- @Override
- public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME)) {
- return;
- }
- getMediaSessionManager().dispatchAdjustVolume(suggestedStreamType, direction, flags);
- }
-
/** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */
@Override
public void setStreamVolumeForUid(int streamType, int index, int flags,
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
deleted file mode 100644
index 4ceb83b2..0000000
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.audio;
-
-import static android.media.audio.Flags.autoPublicVolumeApiHardening;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioManager;
-import android.os.Binder;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * Class to encapsulate all audio API hardening operations
- */
-public class HardeningEnforcer {
-
- private static final String TAG = "AS.HardeningEnforcer";
-
- final Context mContext;
- final boolean mIsAutomotive;
-
- /**
- * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100;
- /**
- * Matches calls from {@link AudioManager#adjustVolume(int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101;
- /**
- * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102;
- /**
- * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103;
- /**
- * Matches calls from {@link AudioManager#setRingerMode(int)}
- */
- public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200;
-
- public HardeningEnforcer(Context ctxt, boolean isAutomotive) {
- mContext = ctxt;
- mIsAutomotive = isAutomotive;
- }
-
- /**
- * Checks whether the call in the current thread should be allowed or blocked
- * @param volumeMethod name of the method to check, for logging purposes
- * @return false if the method call is allowed, true if it should be a no-op
- */
- protected boolean blockVolumeMethod(int volumeMethod) {
- // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED
- if (mIsAutomotive) {
- if (!autoPublicVolumeApiHardening()) {
- // automotive hardening flag disabled, no blocking on auto
- return false;
- }
- if (mContext.checkCallingOrSelfPermission(
- Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
- == PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- if (Binder.getCallingUid() < UserHandle.AID_APP_START) {
- return false;
- }
- // TODO metrics?
- // TODO log for audio dumpsys?
- Log.e(TAG, "Preventing volume method " + volumeMethod + " for "
- + getPackNameForUid(Binder.getCallingUid()));
- return true;
- }
- // not blocking
- return false;
- }
-
- private String getPackNameForUid(int uid) {
- final long token = Binder.clearCallingIdentity();
- try {
- final String[] names = mContext.getPackageManager().getPackagesForUid(uid);
- if (names == null
- || names.length == 0
- || TextUtils.isEmpty(names[0])) {
- return "[" + uid + "]";
- }
- return names[0];
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index ea92154..61e4f36 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -19,6 +19,9 @@
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
import static android.media.AudioSystem.isBluetoothDevice;
+import static android.media.AudioSystem.isBluetoothLeDevice;
+
+import static com.android.media.audio.Flags.dsaOverBtLeAudio;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1625,10 +1628,10 @@
}
private int getHeadSensorHandleUpdateTracker() {
- int headHandle = -1;
+ Sensor htSensor = null;
if (sRoutingDevices.isEmpty()) {
logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker");
- return headHandle;
+ return -1;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);
@@ -1642,27 +1645,86 @@
for (String address : deviceAddresses) {
UUID routingDeviceUuid = UuidUtils.uuidFromAudioDeviceAttributes(
new AudioDeviceAttributes(currentDevice.getInternalType(), address));
- for (Sensor sensor : sensors) {
- final UUID uuid = sensor.getUuid();
- if (uuid.equals(routingDeviceUuid)) {
- headHandle = sensor.getHandle();
- if (!setHasHeadTracker(currentDevice)) {
- headHandle = -1;
+ if (dsaOverBtLeAudio()) {
+ for (Sensor sensor : sensors) {
+ final UUID uuid = sensor.getUuid();
+ if (uuid.equals(routingDeviceUuid)) {
+ htSensor = sensor;
+ HeadtrackerInfo info = new HeadtrackerInfo(sensor);
+ if (isBluetoothLeDevice(currentDevice.getInternalType())) {
+ if (info.getMajorVersion() == 2) {
+ // Version 2 is used only by LE Audio profile
+ break;
+ }
+ // we do not break, as this could be a match on the A2DP sensor
+ // for a dual mode headset.
+ } else if (info.getMajorVersion() == 1) {
+ // Version 1 is used only by A2DP profile
+ break;
+ }
}
+ if (htSensor == null && uuid.equals(UuidUtils.STANDALONE_UUID)) {
+ htSensor = sensor;
+ // we do not break, perhaps we find a head tracker on device.
+ }
+ }
+ if (htSensor != null) {
+ if (htSensor.getUuid().equals(UuidUtils.STANDALONE_UUID)) {
+ break;
+ }
+ if (setHasHeadTracker(currentDevice)) {
+ break;
+ } else {
+ htSensor = null;
+ }
+ }
+ } else {
+ for (Sensor sensor : sensors) {
+ final UUID uuid = sensor.getUuid();
+ if (uuid.equals(routingDeviceUuid)) {
+ htSensor = sensor;
+ if (!setHasHeadTracker(currentDevice)) {
+ htSensor = null;
+ }
+ break;
+ }
+ if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+ htSensor = sensor;
+ // we do not break, perhaps we find a head tracker on device.
+ }
+ }
+ if (htSensor != null) {
break;
}
- if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
- headHandle = sensor.getHandle();
- // we do not break, perhaps we find a head tracker on device.
- }
- }
- if (headHandle != -1) {
- break;
}
}
- return headHandle;
+ return htSensor != null ? htSensor.getHandle() : -1;
}
+ /**
+ * Contains the information parsed from the head tracker sensor version.
+ * See platform/hardware/libhardware/modules/sensors/dynamic_sensor/HidRawSensor.h
+ * for the definition of version and capability fields.
+ */
+ private static class HeadtrackerInfo {
+ private final int mVersion;
+ HeadtrackerInfo(Sensor sensor) {
+ mVersion = sensor.getVersion();
+ }
+ int getMajorVersion() {
+ return (mVersion & 0xFF000000) >> 24;
+ }
+ int getMinorVersion() {
+ return (mVersion & 0xFF0000) >> 16;
+ }
+ boolean hasAclTransport() {
+ return getMajorVersion() == 2 ? ((mVersion & 0x1) != 0) : false;
+ }
+ boolean hasIsoTransport() {
+ return getMajorVersion() == 2 ? ((mVersion & 0x2) != 0) : false;
+ }
+ };
+
private int getScreenSensorHandle() {
int screenHandle = -1;
Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index c629b2b..be78ea2 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -157,4 +157,10 @@
* @see VirtualDevice#getPersistentDeviceId()
*/
public abstract @Nullable String getPersistentIdForDevice(int deviceId);
+
+ /**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ */
+ public abstract @NonNull Set<String> getAllPersistentDeviceIds();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bae06347..c2b5964 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -184,6 +184,7 @@
import android.companion.ICompanionDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.LoggingOnly;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
@@ -555,7 +556,7 @@
* creation and activation of an implicit {@link android.app.AutomaticZenRule}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
static final long MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES = 308670109L;
private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30);
@@ -828,6 +829,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 +1919,6 @@
unhideNotificationsForPackages(pkgList, uidList);
}
}
-
mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList);
}
}
@@ -4021,11 +4037,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 +4232,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 +7288,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 +7400,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 +9435,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/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/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/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 28f656e..8b282dd3 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -58,6 +58,23 @@
}
/**
+ * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+ *
+ * @see WindowProcessController#setReportedProcState(int)
+ */
+ void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem transactionItem) throws RemoteException {
+ // Immediately dispatching to client, and must not access WMS.
+ final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+ if (transactionItem.isActivityLifecycleItem()) {
+ clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
+ } else {
+ clientTransaction.addCallback(transactionItem);
+ }
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
* Schedules a single transaction item, either a callback or a lifecycle request, delivery to
* client application.
* @throws RemoteException
@@ -65,6 +82,7 @@
*/
void scheduleTransactionItem(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
+ // TODO(b/260873529): queue the transaction items.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
if (transactionItem.isActivityLifecycleItem()) {
clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
@@ -82,6 +100,7 @@
void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem,
@NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+ // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
clientTransaction.addCallback(transactionItem);
clientTransaction.setLifecycleStateRequest(lifecycleItem);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 558bf9d..2b18f07 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -388,13 +388,22 @@
final IApplicationThread thread = mThread;
if (prevProcState >= CACHED_CONFIG_PROC_STATE && repProcState < CACHED_CONFIG_PROC_STATE
&& thread != null && mHasCachedConfiguration) {
- final Configuration config;
+ final ConfigurationChangeItem configurationChangeItem;
synchronized (mLastReportedConfiguration) {
- config = new Configuration(mLastReportedConfiguration);
+ onConfigurationChangePreScheduled(mLastReportedConfiguration);
+ configurationChangeItem = ConfigurationChangeItem.obtain(
+ mLastReportedConfiguration, mLastTopActivityDeviceId);
}
// Schedule immediately to make sure the app component (e.g. receiver, service) can get
// the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
- scheduleConfigurationChange(thread, config);
+ try {
+ // No WM lock here.
+ mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+ thread, configurationChangeItem);
+ } catch (Exception e) {
+ Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
+ + configurationChangeItem + " owner=" + mOwner, e);
+ }
}
}
@@ -1634,11 +1643,12 @@
}
}
- scheduleConfigurationChange(thread, config);
+ onConfigurationChangePreScheduled(config);
+ scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
+ config, mLastTopActivityDeviceId));
}
- private void scheduleConfigurationChange(@NonNull IApplicationThread thread,
- @NonNull Configuration config) {
+ private void onConfigurationChangePreScheduled(@NonNull Configuration config) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config);
if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1646,8 +1656,6 @@
Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
}
mHasCachedConfiguration = false;
- scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
- config, mLastTopActivityDeviceId));
}
@VisibleForTesting
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/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index d87b8d1..b5ba322 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -773,6 +773,30 @@
}
@Test
+ public void getAllPersistentDeviceIds_respectsCurrentAssociations() {
+ mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo));
+ TestableLooper.get(this).processAllMessages();
+
+ assertThat(mLocalService.getAllPersistentDeviceIds())
+ .containsExactly(mDeviceImpl.getPersistentDeviceId());
+
+ mVdms.onCdmAssociationsChanged(List.of(
+ createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING),
+ createAssociationInfo(3, AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION),
+ createAssociationInfo(4, AssociationRequest.DEVICE_PROFILE_WATCH)));
+ TestableLooper.get(this).processAllMessages();
+
+ assertThat(mLocalService.getAllPersistentDeviceIds()).containsExactly(
+ VirtualDeviceImpl.createPersistentDeviceId(2),
+ VirtualDeviceImpl.createPersistentDeviceId(3));
+
+ mVdms.onCdmAssociationsChanged(Collections.emptyList());
+ TestableLooper.get(this).processAllMessages();
+
+ assertThat(mLocalService.getAllPersistentDeviceIds()).isEmpty();
+ }
+
+ @Test
public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
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/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e152feb..e31ee11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -306,7 +306,7 @@
@Test
public void testCachedStateConfigurationChange() throws RemoteException {
- doNothing().when(mClientLifecycleManager).scheduleTransactionItem(any(), any());
+ doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
final IApplicationThread thread = mWpc.getThread();
final Configuration newConfig = new Configuration(mWpc.getConfiguration());
newConfig.densityDpi += 100;
@@ -322,18 +322,17 @@
newConfig.densityDpi += 100;
mWpc.onConfigurationChanged(newConfig);
verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
+ verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(eq(thread), any());
// Cached -> non-cached will send the previous deferred config immediately.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
final ArgumentCaptor<ConfigurationChangeItem> captor =
ArgumentCaptor.forClass(ConfigurationChangeItem.class);
- verify(mClientLifecycleManager).scheduleTransactionItem(eq(thread), captor.capture());
+ verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+ eq(thread), captor.capture());
final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
captor.getValue().preExecute(client);
- final ArgumentCaptor<Configuration> configCaptor =
- ArgumentCaptor.forClass(Configuration.class);
- verify(client).updatePendingConfiguration(configCaptor.capture());
- assertEquals(newConfig, configCaptor.getValue());
+ verify(client).updatePendingConfiguration(newConfig);
}
@Test
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index b44f1a6..c49f8fe 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -23,10 +23,13 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,7 +56,12 @@
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
}
- transitions { testApp.finishActivity(wmHelper) }
+ transitions {
+ broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY)
+ wmHelper.StateSyncBuilder()
+ .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
+ .waitForAndVerify()
+ }
teardown { simpleApp.exit(wmHelper) }
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 976ac82..994edc5 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -28,6 +28,7 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,7 +54,11 @@
// Enable letterbox when the app calls setRequestedOrientation
device.executeShellCommand("cmd window set-ignore-orientation-request true")
}
- transitions { testApp.toggleFixPortraitOrientation(wmHelper) }
+ transitions {
+ broadcastActionTrigger.doAction(ACTION_TOGGLE_ORIENTATION)
+ // Ensure app relaunching transition finished and the IME was shown
+ testApp.waitIMEShown(wmHelper)
+ }
teardown {
testApp.exit()
device.executeShellCommand("cmd window set-ignore-orientation-request false")
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index aff8e65..6ee5a9a 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -104,7 +104,7 @@
@Presubmit
@Test
- open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
+ fun imeLayerIsVisibleWhenSwitchingToImeApp() {
flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 4ffdcea..1ad5c0d 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -93,7 +93,7 @@
}
transitions {
testApp.launchViaIntent(wmHelper)
- wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
+ testApp.waitIMEShown(wmHelper)
}
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 6ad235c..181a2a2 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -23,11 +23,14 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
import android.view.WindowInsets.Type.ime
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
@@ -50,8 +53,12 @@
override val transition: FlickerBuilder.() -> Unit = {
setup {
testApp.launchViaIntent(wmHelper)
- wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
- testApp.startDialogThemedActivity(wmHelper)
+ testApp.waitIMEShown(wmHelper)
+ broadcastActionTrigger.doAction(ACTION_START_DIALOG_THEMED_ACTIVITY)
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(
+ ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
+ .waitForAndVerify()
// Verify IME insets isn't visible on dialog since it's non-IME focusable window
assertFalse(testApp.getInsetsVisibleFromDialog(ime()))
assertTrue(testApp.getInsetsVisibleFromDialog(statusBars()))
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 7c9c05d..ad272a0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker
import android.app.Instrumentation
+import android.content.Intent
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerBuilderProvider
@@ -50,6 +51,19 @@
/** Specification of the test transition to execute */
abstract val transition: FlickerBuilder.() -> Unit
+ protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
+
+ // Helper class to process test actions by broadcast.
+ protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
+ private fun createIntentWithAction(broadcastAction: String): Intent {
+ return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ }
+
+ fun doAction(broadcastAction: String) {
+ instrumentation.context.sendBroadcast(createIntentWithAction(broadcastAction))
+ }
+ }
+
/**
* Entry point for the test runner. It will use this method to initialize and cache flicker
* executions
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index 252f7d3..cb1aab0 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -50,7 +50,7 @@
waitIMEShown(wmHelper)
}
- protected fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
+ fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
}
@@ -63,17 +63,4 @@
uiDevice.pressBack()
wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify()
}
-
- open fun finishActivity(wmHelper: WindowManagerStateHelper) {
- val finishButton =
- uiDevice.wait(
- Until.findObject(By.res(packageName, "finish_activity_btn")),
- FIND_TIMEOUT
- )
- requireNotNull(finishButton) {
- "Finish activity button not found, probably IME activity is not on the screen?"
- }
- finishButton.click()
- wmHelper.StateSyncBuilder().withActivityRemoved(this).waitForAndVerify()
- }
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index d3cee64..0ee7aee 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -74,24 +74,6 @@
open(expectedPackage)
}
- fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
- val button =
- uiDevice.wait(
- Until.findObject(By.res(packageName, "start_dialog_themed_activity_btn")),
- FIND_TIMEOUT
- )
-
- requireNotNull(button) {
- "Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. Screen turned off)"
- }
- button.click()
- wmHelper
- .StateSyncBuilder()
- .withFullScreenApp(ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
- .waitForAndVerify()
- }
-
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dialog = uiDevice.wait(Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
@@ -126,20 +108,4 @@
}
return false
}
-
- fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
- val button =
- uiDevice.wait(
- Until.findObject(By.res(packageName, "toggle_fixed_portrait_btn")),
- FIND_TIMEOUT
- )
- require(button != null) {
- "Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. Screen turned off)"
- }
- button.click()
- instrumentation.waitForIdleSync()
- // Ensure app relaunching transition finish and the IME has shown
- waitIMEShown(wmHelper)
- }
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index fa73e2c..507c1b6 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,39 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
+ android:background="@android:color/holo_green_light"
android:focusableInTouchMode="true"
- android:background="@android:color/holo_green_light">
- <EditText android:id="@+id/plain_text_input"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:imeOptions="flagNoExtractUi"
- android:inputType="text"/>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/plain_text_input"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
- <Button
- android:id="@+id/finish_activity_btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Finish activity" />
- <Button
- android:id="@+id/start_dialog_themed_activity_btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Dialog themed activity" />
- <ToggleButton
- android:id="@+id/toggle_fixed_portrait_btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textOn="Portrait (On)"
- android:textOff="Portrait (Off)"
- />
- </LinearLayout>
+ android:layout_height="wrap_content"
+ android:imeOptions="flagNoExtractUi"
+ android:inputType="text" />
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 8b334c0..80c1dd0 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -40,6 +40,18 @@
public static final String LABEL = "ImeActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeActivity");
+
+ /** Intent action used to finish the test activity. */
+ public static final String ACTION_FINISH_ACTIVITY =
+ FLICKER_APP_PACKAGE + ".ImeActivity.FINISH_ACTIVITY";
+
+ /** Intent action used to start a {@link DialogThemedActivity}. */
+ public static final String ACTION_START_DIALOG_THEMED_ACTIVITY =
+ FLICKER_APP_PACKAGE + ".ImeActivity.START_DIALOG_THEMED_ACTIVITY";
+
+ /** Intent action used to toggle activity orientation. */
+ public static final String ACTION_TOGGLE_ORIENTATION =
+ FLICKER_APP_PACKAGE + ".ImeActivity.TOGGLE_ORIENTATION";
}
public static class AutoFocusActivity {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
index d7ee2af..4418b5a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
@@ -16,12 +16,51 @@
package com.android.server.wm.flicker.testapp;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION;
+
import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Bundle;
+import android.util.Log;
import android.view.WindowManager;
-import android.widget.Button;
public class ImeActivity extends Activity {
+
+ private static final String TAG = "ImeActivity";
+
+ /** Receiver used to handle actions coming from the test helper methods. */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case ACTION_FINISH_ACTIVITY -> finish();
+ case ACTION_START_DIALOG_THEMED_ACTIVITY -> startActivity(
+ new Intent(context, DialogThemedActivity.class));
+ case ACTION_TOGGLE_ORIENTATION -> {
+ mIsPortrait = !mIsPortrait;
+ setRequestedOrientation(mIsPortrait
+ ? SCREEN_ORIENTATION_PORTRAIT
+ : SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+ default -> Log.w(TAG, "Unhandled action=" + intent.getAction());
+ }
+ }
+ };
+
+ /**
+ * Used to toggle activity orientation between portrait when {@code true} and
+ * unspecified otherwise.
+ */
+ private boolean mIsPortrait = false;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -30,9 +69,17 @@
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(p);
setContentView(R.layout.activity_ime);
- Button button = findViewById(R.id.finish_activity_btn);
- button.setOnClickListener(view -> {
- finish();
- });
+
+ final var filter = new IntentFilter();
+ filter.addAction(ACTION_FINISH_ACTIVITY);
+ filter.addAction(ACTION_START_DIALOG_THEMED_ACTIVITY);
+ filter.addAction(ACTION_TOGGLE_ORIENTATION);
+ registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mBroadcastReceiver);
+ super.onDestroy();
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
index 7ee8deb..cd711f7 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -16,29 +16,12 @@
package com.android.server.wm.flicker.testapp;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-
-import android.content.Intent;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ToggleButton;
-
public class ImeActivityAutoFocus extends ImeActivity {
@Override
protected void onStart() {
super.onStart();
- Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn);
- startThemedActivityButton.setOnClickListener(
- button -> startActivity(new Intent(this, DialogThemedActivity.class)));
-
- ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn);
- toggleFixedPortraitButton.setOnCheckedChangeListener(
- (button, isChecked) -> setRequestedOrientation(
- isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED));
-
- EditText editTextField = findViewById(R.id.plain_text_input);
+ final var editTextField = findViewById(R.id.plain_text_input);
editTextField.requestFocus();
}
}
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