Merge "Refresh tiles on user change" into main
diff --git a/apct-tests/perftests/protolog/Android.bp b/apct-tests/perftests/protolog/Android.bp
new file mode 100644
index 0000000..08e365b
--- /dev/null
+++ b/apct-tests/perftests/protolog/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 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.
+
+android_test {
+ name: "ProtologPerfTests",
+ team: "trendy_team_windowing_tools",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.annotation_annotation",
+ "apct-perftests-utils",
+ "collector-device-lib",
+ "platform-test-annotations",
+ ],
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+ data: [":perfetto_artifacts"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/apct-tests/perftests/protolog/AndroidManifest.xml b/apct-tests/perftests/protolog/AndroidManifest.xml
new file mode 100644
index 0000000..68125df
--- /dev/null
+++ b/apct-tests/perftests/protolog/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.perftests.protolog">
+
+ <!-- For perfetto trace files -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.protolog">
+ <!-- <meta-data android:name="listener" android:value="android.protolog.ProtologPerfRunListener" /> -->
+ </instrumentation>
+</manifest>
diff --git a/apct-tests/perftests/protolog/AndroidTest.xml b/apct-tests/perftests/protolog/AndroidTest.xml
new file mode 100644
index 0000000..871a20c
--- /dev/null
+++ b/apct-tests/perftests/protolog/AndroidTest.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Runs ProtologPerfTests metric instrumentation.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-metric-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="ProtologPerfTests.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props" value="true" />
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="cmd window dismiss-keyguard" />
+ <option name="run-command" value="cmd package compile -m speed com.android.perftests.wm" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+ </target_preparer>
+
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.perftests.protolog" />
+ <option name="hidden-api-checks" value="false"/>
+
+ <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+ <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+
+ <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ <!-- ProcLoadListener related arguments -->
+ <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+ <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+ <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+ <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+ <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+ </test>
+
+ <!-- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/local/tmp/ProtologPerfTests" /> -->
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <!-- <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector> -->
+</configuration>
diff --git a/apct-tests/perftests/protolog/OWNERS b/apct-tests/perftests/protolog/OWNERS
new file mode 100644
index 0000000..3f3308c
--- /dev/null
+++ b/apct-tests/perftests/protolog/OWNERS
@@ -0,0 +1 @@
+include platform/development:/tools/winscope/OWNERS
diff --git a/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java b/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java
new file mode 100644
index 0000000..e1edb37
--- /dev/null
+++ b/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 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.internal.protolog;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogLevel;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public class ProtologPerfTest {
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameters(name="logToProto_{0}_logToLogcat_{1}")
+ public static Collection<Object[]> params() {
+ return Arrays.asList(new Object[][] {
+ { true, true },
+ { true, false },
+ { false, true },
+ { false, false }
+ });
+ }
+
+ private final boolean mLogToProto;
+ private final boolean mLogToLogcat;
+
+ public ProtologPerfTest(boolean logToProto, boolean logToLogcat) {
+ mLogToProto = logToProto;
+ mLogToLogcat = logToLogcat;
+ }
+
+ @BeforeClass
+ public static void init() {
+ ProtoLog.init(TestProtoLogGroup.values());
+ }
+
+ @Before
+ public void setUp() {
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(mLogToProto);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(mLogToLogcat);
+ }
+
+ @Test
+ public void logProcessedProtoLogMessageWithoutArgs() {
+ final var protoLog = ProtoLog.getSingleInstance();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ protoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
+ 0, (Object[]) null);
+ }
+ }
+
+ @Test
+ public void logProcessedProtoLogMessageWithArgs() {
+ final var protoLog = ProtoLog.getSingleInstance();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ protoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
+ 0b1110101001010100,
+ new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+ }
+ }
+
+ @Test
+ public void logNonProcessedProtoLogMessageWithNoArgs() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message");
+ }
+ }
+
+ @Test
+ public void logNonProcessedProtoLogMessageWithArgs() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test messag %s, %d, %b", "arg1", 2, true);
+ }
+ }
+
+ private enum TestProtoLogGroup implements IProtoLogGroup {
+ TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation, they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+ }
+}
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index d991da5..b3a674f 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -890,7 +890,7 @@
cmd: "rm -f $(genDir)/framework.aidl.merged && " +
"for i in $(in); do " +
" rm -f $(genDir)/framework.aidl.tmp && " +
- " $(location sdkparcelables) $$i $(genDir)/framework.aidl.tmp && " +
+ " $(location sdkparcelables) $$i $(genDir)/framework.aidl.tmp --guarantee_stable && " +
" cat $(genDir)/framework.aidl.tmp >> $(genDir)/framework.aidl.merged; " +
"done && " +
"sort -u $(genDir)/framework.aidl.merged > $(out)",
diff --git a/core/api/current.txt b/core/api/current.txt
index c7df662..861be40 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7968,7 +7968,7 @@
field public static final String PERMISSION_GRANT_POLICY = "permissionGrant";
field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity";
field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken";
- field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String SECURITY_LOGGING_POLICY = "securityLogging";
+ field public static final String SECURITY_LOGGING_POLICY = "securityLogging";
field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled";
field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages";
@@ -54922,6 +54922,8 @@
method @Deprecated public void addAction(int);
method public void addChild(android.view.View);
method public void addChild(android.view.View, int);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
method public boolean canOpenPopup();
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54950,6 +54952,7 @@
method public int getInputType();
method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
method public int getLiveRegion();
method public int getMaxTextLength();
method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -55010,6 +55013,8 @@
method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
method public boolean removeChild(android.view.View);
method public boolean removeChild(android.view.View, int);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
method public void setAccessibilityDataSensitive(boolean);
method public void setAccessibilityFocused(boolean);
method public void setAvailableExtraData(java.util.List<java.lang.String>);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e40cbc1..e1e63cc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -201,7 +201,7 @@
field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
- field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
+ field public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
@@ -1296,7 +1296,7 @@
}
public final class DevicePolicyIdentifiers {
- field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String AUDIT_LOGGING_POLICY = "auditLogging";
+ field public static final String AUDIT_LOGGING_POLICY = "auditLogging";
}
public class DevicePolicyKeyguardService extends android.app.Service {
@@ -1308,7 +1308,7 @@
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
@@ -1328,7 +1328,7 @@
method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
method public boolean isDeviceManaged();
method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE) public boolean isDevicePotentiallyStolen();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
@@ -1344,8 +1344,8 @@
method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
@@ -14219,7 +14219,7 @@
field public static final int CAPABILITY_EMERGENCY_PREFERRED = 8192; // 0x2000
field public static final int CAPABILITY_EMERGENCY_VIDEO_CALLING = 512; // 0x200
field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
- field public static final String EXTRA_PLAY_CALL_RECORDING_TONE = "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
+ field @Deprecated @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_PLAY_CALL_RECORDING_TONE = "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_SKIP_CALL_FILTERING = "android.telecom.extra.SKIP_CALL_FILTERING";
field public static final String EXTRA_SORT_ORDER = "android.telecom.extra.SORT_ORDER";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8dd4adc..ec23cfe 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1992,6 +1992,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice();
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isVolumeControlUsingVolumeGroups();
+ method public void permissionUpdateBarrier();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setCsd(float);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setNotifAliasRingForTest(boolean);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d455853..4350545 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4583,7 +4583,7 @@
public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
@Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
@NonNull SurfaceControl startingWindowLeash) {
- final DecorView decorView = (DecorView) r.window.peekDecorView();
+ final DecorView decorView = r.window != null ? (DecorView) r.window.peekDecorView() : null;
if (parcelable != null && decorView != null) {
createSplashScreen(r, decorView, parcelable, startingWindowLeash);
} else {
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index eeaf0b3..156512a 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -17,7 +17,6 @@
package android.app.admin;
import static android.app.admin.flags.Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -50,7 +49,6 @@
/**
* String identifier for {@link DevicePolicyManager#setSecurityLoggingEnabled}.
*/
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
public static final String SECURITY_LOGGING_POLICY = "securityLogging";
/**
@@ -58,7 +56,6 @@
*
* @hide
*/
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@SystemApi
public static final String AUDIT_LOGGING_POLICY = "auditLogging";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ba1dc56..5088ea6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -60,7 +60,6 @@
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED;
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
@@ -14335,7 +14334,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void setAuditLogEnabled(boolean enabled) {
throwIfParentInstance("setAuditLogEnabled");
@@ -14352,7 +14350,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public boolean isAuditLogEnabled() {
throwIfParentInstance("isAuditLogEnabled");
@@ -14374,7 +14371,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void setAuditLogEventCallback(
@NonNull @CallbackExecutor Executor executor,
@@ -14401,7 +14397,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void clearAuditLogEventCallback() {
throwIfParentInstance("clearAuditLogEventCallback");
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 9148e3c..56f4792 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -105,6 +105,7 @@
bug: "289520697"
}
+# Fully rolled out and must not be used.
flag {
name: "security_log_v2_enabled"
is_exported: true
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ffcb1cb..9c711bc 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -106,6 +106,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.compat.IPlatformCompatNative;
+import com.android.internal.protolog.ProtoLogConfigurationService;
import java.io.File;
import java.io.FileInputStream;
@@ -6701,13 +6702,13 @@
/**
* Use with {@link #getSystemService(String)} to retrieve the
- * {@link com.android.internal.protolog.ProtoLogService} for registering ProtoLog clients.
+ * {@link ProtoLogConfigurationService} for registering ProtoLog clients.
*
* @see #getSystemService(String)
- * @see com.android.internal.protolog.ProtoLogService
+ * @see ProtoLogConfigurationService
* @hide
*/
- public static final String PROTOLOG_SERVICE = "protolog";
+ public static final String PROTOLOG_CONFIGURATION_SERVICE = "protolog_configuration";
/**
* Determine whether the given permission is allowed for a particular
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 7b18117..0a264e3 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -26,6 +26,7 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
@@ -143,6 +144,7 @@
* @attr ref android.R.styleable#ColorStateListItem_color
* @attr ref android.R.styleable#ColorStateListItem_lStar
*/
+@RavenwoodKeepWholeClass
public class ColorStateList extends ComplexColor implements Parcelable {
private static final String TAG = "ColorStateList";
diff --git a/core/java/android/content/res/ComplexColor.java b/core/java/android/content/res/ComplexColor.java
index 58c6fc5..a385ee3 100644
--- a/core/java/android/content/res/ComplexColor.java
+++ b/core/java/android/content/res/ComplexColor.java
@@ -18,13 +18,14 @@
import android.annotation.ColorInt;
import android.content.res.Resources.Theme;
-import android.graphics.Color;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* Defines an abstract class for the complex color information, like
* {@link android.content.res.ColorStateList} or {@link android.content.res.GradientColor}
* @hide
*/
+@RavenwoodKeepWholeClass
public abstract class ComplexColor {
private int mChangingConfigurations;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index bf4d97d..0559631 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1124,7 +1124,6 @@
*/
@NonNull
@Deprecated
- @RavenwoodThrow(blockedBy = ColorStateList.class)
public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {
final ColorStateList csl = getColorStateList(id, null);
if (csl != null && csl.canApplyTheme()) {
@@ -1155,7 +1154,6 @@
* color or multiple colors that can be selected based on a state.
*/
@NonNull
- @RavenwoodThrow(blockedBy = ColorStateList.class)
public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
@@ -1169,7 +1167,6 @@
}
@NonNull
- @RavenwoodThrow(blockedBy = ColorStateList.class)
ColorStateList loadColorStateList(@NonNull TypedValue value, int id, @Nullable Theme theme)
throws NotFoundException {
return mResourcesImpl.loadColorStateList(this, value, id, theme);
@@ -1179,7 +1176,6 @@
* @hide
*/
@NonNull
- @RavenwoodThrow(blockedBy = ComplexColor.class)
public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, @Nullable Theme theme) {
return mResourcesImpl.loadComplexColor(this, value, id, theme);
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 90420de..e6b9342 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1126,7 +1126,6 @@
}
@Nullable
- @RavenwoodThrow(blockedBy = ComplexColor.class)
ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
Resources.Theme theme) {
if (TRACE_FOR_PRELOAD) {
@@ -1168,7 +1167,6 @@
}
@NonNull
- @RavenwoodThrow(blockedBy = ColorStateList.class)
ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Resources.Theme theme)
throws NotFoundException {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index f8eeaa9..79185a1 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -28,7 +28,6 @@
import android.os.Build;
import android.os.StrictMode;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -598,7 +597,6 @@
* not an integer color or color state list.
*/
@Nullable
- @RavenwoodThrow(blockedBy = ColorStateList.class)
public ColorStateList getColorStateList(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c7751e3..c4d12d4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1997,6 +1997,8 @@
STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG
| STATE2_GPS_SIGNAL_QUALITY_MASK;
+ public static final int GNSS_SIGNAL_QUALITY_NONE = 2;
+
@UnsupportedAppUsage
public int states2;
@@ -2220,7 +2222,7 @@
modemRailChargeMah = 0;
wifiRailChargeMah = 0;
states = 0;
- states2 = 0;
+ states2 = GNSS_SIGNAL_QUALITY_NONE << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
wakelockTag = null;
wakeReasonTag = null;
eventCode = EVENT_NONE;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b7556df..4bc3dbe 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -708,9 +708,16 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
public final native void markVintfStability();
+ /** @hide */
+ private void markVintfStability$ravenwood() {
+ // This is not useful for Ravenwood which uses local binder.
+ // TODO(b/361785059): Use real native libbinder.
+ }
+
/**
* Use a VINTF-stability binder w/o VINTF requirements. Should be called
* on a binder before it is sent out of process.
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 0242de0..c3585e3 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -81,6 +81,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.function.Consumer;
/**
@@ -1261,7 +1262,7 @@
@Override
public final IBinder onBind(Intent intent) {
if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
- mDreamServiceWrapper = new DreamServiceWrapper();
+ mDreamServiceWrapper = new DreamServiceWrapper(new WeakReference<>(this));
final ComponentName overlayComponent = intent.getParcelableExtra(
EXTRA_DREAM_OVERLAY_COMPONENT, ComponentName.class);
@@ -1631,7 +1632,8 @@
i.setComponent(mInjector.getDreamActivityComponent());
i.setPackage(mInjector.getDreamPackageName());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
- DreamActivity.setCallback(i, new DreamActivityCallbacks(mDreamToken));
+ DreamActivity.setCallback(i,
+ new DreamActivityCallbacks(mDreamToken, new WeakReference<>(this)));
final ServiceInfo serviceInfo = mInjector.getServiceInfo();
final CharSequence title = fetchDreamLabel(mInjector.getPackageManager(),
mInjector.getResources(), serviceInfo, isPreviewMode);
@@ -1845,22 +1847,37 @@
* uses it to control the DreamService. It is also used to receive callbacks from the
* DreamActivity.
*/
- final class DreamServiceWrapper extends IDreamService.Stub {
+ static final class DreamServiceWrapper extends IDreamService.Stub {
+ final WeakReference<DreamService> mService;
+
+ DreamServiceWrapper(WeakReference<DreamService> service) {
+ mService = service;
+ }
+
+ private void post(Consumer<DreamService> consumer) {
+ final DreamService service = mService.get();
+
+ if (service == null) {
+ return;
+ }
+
+ service.mHandler.post(() -> consumer.accept(service));
+ }
+
@Override
public void attach(final IBinder dreamToken, final boolean canDoze,
final boolean isPreviewMode, IRemoteCallback started) {
- mHandler.post(
- () -> DreamService.this.attach(dreamToken, canDoze, isPreviewMode, started));
+ post(dreamService -> dreamService.attach(dreamToken, canDoze, isPreviewMode, started));
}
@Override
public void detach() {
- mHandler.post(DreamService.this::detach);
+ post(DreamService::detach);
}
@Override
public void wakeUp() {
- mHandler.post(() -> DreamService.this.wakeUp(true /*fromSystem*/));
+ post(dreamService -> dreamService.wakeUp(true /*fromSystem*/));
}
@Override
@@ -1868,48 +1885,70 @@
if (!dreamHandlesBeingObscured()) {
return;
}
-
- mHandler.post(DreamService.this::comeToFront);
+ post(DreamService::comeToFront);
}
}
+ private void onActivityCreated(DreamActivity activity, IBinder dreamToken) {
+ if (dreamToken != mDreamToken || mFinished) {
+ Slog.d(TAG, "DreamActivity was created after the dream was finished or "
+ + "a new dream started, finishing DreamActivity");
+ if (!activity.isFinishing()) {
+ activity.finishAndRemoveTask();
+ }
+ return;
+ }
+ if (mActivity != null) {
+ Slog.w(TAG, "A DreamActivity has already been started, "
+ + "finishing latest DreamActivity");
+ if (!activity.isFinishing()) {
+ activity.finishAndRemoveTask();
+ }
+ return;
+ }
+
+ mActivity = activity;
+ onWindowCreated(activity.getWindow());
+ }
+
+ private void onActivityDestroyed() {
+ mActivity = null;
+ mWindow = null;
+ detach();
+ }
+
/** @hide */
@VisibleForTesting
- public final class DreamActivityCallbacks extends Binder {
+ public static final class DreamActivityCallbacks extends Binder {
private final IBinder mActivityDreamToken;
+ private WeakReference<DreamService> mService;
- DreamActivityCallbacks(IBinder token) {
+ DreamActivityCallbacks(IBinder token, WeakReference<DreamService> service) {
mActivityDreamToken = token;
+ mService = service;
}
/** Callback when the {@link DreamActivity} has been created */
public void onActivityCreated(DreamActivity activity) {
- if (mActivityDreamToken != mDreamToken || mFinished) {
- Slog.d(TAG, "DreamActivity was created after the dream was finished or "
- + "a new dream started, finishing DreamActivity");
- if (!activity.isFinishing()) {
- activity.finishAndRemoveTask();
- }
- return;
- }
- if (mActivity != null) {
- Slog.w(TAG, "A DreamActivity has already been started, "
- + "finishing latest DreamActivity");
- if (!activity.isFinishing()) {
- activity.finishAndRemoveTask();
- }
+ final DreamService service = mService.get();
+
+ if (service == null) {
return;
}
- mActivity = activity;
- onWindowCreated(activity.getWindow());
+ service.onActivityCreated(activity, mActivityDreamToken);
}
/** Callback when the {@link DreamActivity} has been destroyed */
public void onActivityDestroyed() {
- mActivity = null;
- mWindow = null;
- detach();
+ final DreamService service = mService.get();
+
+ if (service == null) {
+ return;
+ }
+
+ service.onActivityDestroyed();
+ mService = null;
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 57acc71..918e591 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -241,10 +241,11 @@
// ZenModeConfig XML versions distinguishing key changes.
public static final int XML_VERSION_ZEN_UPGRADE = 8;
public static final int XML_VERSION_MODES_API = 11;
+ public static final int XML_VERSION_MODES_UI = 12;
- // TODO: b/310620812 - Update XML_VERSION and update default_zen_config.xml accordingly when
- // modes_api is inlined.
- private static final int XML_VERSION = 10;
+ // TODO: b/310620812, b/344831624 - Update XML_VERSION and update default_zen_config.xml
+ // accordingly when modes_api / modes_ui are inlined.
+ private static final int XML_VERSION_PRE_MODES = 10;
public static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
@@ -952,7 +953,13 @@
}
public static int getCurrentXmlVersion() {
- return Flags.modesApi() ? XML_VERSION_MODES_API : XML_VERSION;
+ if (Flags.modesUi()) {
+ return XML_VERSION_MODES_UI;
+ } else if (Flags.modesApi()) {
+ return XML_VERSION_MODES_API;
+ } else {
+ return XML_VERSION_PRE_MODES;
+ }
}
public static ZenModeConfig readXml(TypedXmlPullParser parser)
@@ -2607,7 +2614,7 @@
@AutomaticZenRule.Type
public int type = AutomaticZenRule.TYPE_UNKNOWN;
public String triggerDescription;
- public String iconResName;
+ @Nullable public String iconResName;
public boolean allowManualInvocation;
@AutomaticZenRule.ModifiableField public int userModifiedFields;
@ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields;
diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java
index 17adb32..25f321e 100644
--- a/core/java/android/util/StateSet.java
+++ b/core/java/android/util/StateSet.java
@@ -16,6 +16,8 @@
package android.util;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
import com.android.internal.R;
/**
@@ -34,6 +36,7 @@
* and not have static methods here but there is some concern about
* performance since these methods are called during view drawing.
*/
+@RavenwoodKeepWholeClass
public class StateSet {
/**
* The order here is very important to
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 8912035..ab9bd1f 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -730,13 +730,13 @@
/* The distance between point (x, y) and rect, there are 2 basic cases:
* a) The distance is the distance from (x, y) to the closest corner on rect.
- * o | |
+ * o | |
* ---+-----+---
* | |
* ---+-----+---
* | |
* b) The distance is the distance from (x, y) to the closest edge on rect.
- * | o |
+ * | o |
* ---+-----+---
* | |
* ---+-----+---
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 6568912..91e9230 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -35,8 +35,8 @@
import static android.view.InsetsController.LayoutInsetsDuringAnimation;
import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsSource.SIDE_BOTTOM;
-import static android.view.InsetsSource.SIDE_NONE;
import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_NONE;
import static android.view.InsetsSource.SIDE_RIGHT;
import static android.view.InsetsSource.SIDE_TOP;
import static android.view.WindowInsets.Type.ime;
@@ -100,6 +100,8 @@
private @InsetsType int mControllingTypes;
private final InsetsAnimationControlCallbacks mController;
private final WindowInsetsAnimation mAnimation;
+ private final long mDurationMs;
+ private final Interpolator mInterpolator;
/** @see WindowInsetsAnimationController#hasZeroInsetsIme */
private final boolean mHasZeroInsetsIme;
private final CompatibilityInfo.Translator mTranslator;
@@ -120,8 +122,8 @@
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
- @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
- Interpolator interpolator, @AnimationType int animationType,
+ @InsetsType int types, InsetsAnimationControlCallbacks controller,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
mControls = controls;
@@ -155,8 +157,10 @@
}
mPendingInsets = mCurrentInsets;
- mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
- durationMs);
+ mDurationMs = insetsAnimationSpec.getDurationMs(mHasZeroInsetsIme);
+ mInterpolator = insetsAnimationSpec.getInsetsInterpolator(mHasZeroInsetsIme);
+
+ mAnimation = new WindowInsetsAnimation(mTypes, mInterpolator, mDurationMs);
mAnimation.setAlpha(getCurrentAlpha());
mAnimationType = animationType;
mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
@@ -186,6 +190,16 @@
}
@Override
+ public long getDurationMs() {
+ return mDurationMs;
+ }
+
+ @Override
+ public Interpolator getInsetsInterpolator() {
+ return mInterpolator;
+ }
+
+ @Override
public void setReadyDispatched(boolean dispatched) {
mReadyDispatched = dispatched;
}
diff --git a/core/java/android/view/InsetsAnimationSpec.java b/core/java/android/view/InsetsAnimationSpec.java
new file mode 100644
index 0000000..7ad6661
--- /dev/null
+++ b/core/java/android/view/InsetsAnimationSpec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.view;
+
+import android.view.animation.Interpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Used by {@link InsetsAnimationControlImpl}
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public interface InsetsAnimationSpec {
+ /**
+ * @param hasZeroInsetsIme whether IME has no insets (floating, fullscreen or non-overlapping).
+ * @return Duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+ */
+ long getDurationMs(boolean hasZeroInsetsIme);
+ /**
+ * @param hasZeroInsetsIme whether IME has no insets (floating, fullscreen or non-overlapping).
+ * @return The interpolator used for the animation
+ */
+ Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme);
+}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 1b3b3eb..fc185bc 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -33,7 +33,6 @@
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
-import android.view.animation.Interpolator;
import android.view.inputmethod.ImeTracker;
/**
@@ -110,15 +109,15 @@
@UiThread
public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
- @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
- Interpolator interpolator, @AnimationType int animationType,
+ @InsetsType int types, InsetsAnimationControlCallbacks controller,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CompatibilityInfo.Translator translator, Handler mainThreadHandler,
@Nullable ImeTracker.Token statsToken) {
mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller;
mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
- mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+ mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
translator, statsToken);
InsetsAnimationThread.getHandler().post(() -> {
if (mControl.isCancelled()) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7896cbd..b1df51f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -366,7 +366,7 @@
* animate insets.
*/
public static class InternalAnimationControlListener
- implements WindowInsetsAnimationControlListener {
+ implements WindowInsetsAnimationControlListener, InsetsAnimationSpec {
private WindowInsetsAnimationController mController;
private ValueAnimator mAnimator;
@@ -374,7 +374,6 @@
private final boolean mHasAnimationCallbacks;
private final @InsetsType int mRequestedTypes;
private final @Behavior int mBehavior;
- private final long mDurationMs;
private final boolean mDisable;
private final int mFloatingImeBottomInset;
private final WindowInsetsAnimationControlListener mLoggingListener;
@@ -388,7 +387,6 @@
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
mBehavior = behavior;
- mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
mLoggingListener = loggingListener;
@@ -407,13 +405,14 @@
onAnimationFinish();
return;
}
+ final boolean hasZeroInsetsIme = controller.hasZeroInsetsIme();
mAnimator = ValueAnimator.ofFloat(0f, 1f);
- mAnimator.setDuration(mDurationMs);
+ mAnimator.setDuration(controller.getDurationMs());
mAnimator.setInterpolator(new LinearInterpolator());
Insets hiddenInsets = controller.getHiddenStateInsets();
// IME with zero insets is a special case: it will animate-in from offscreen and end
// with final insets of zero and vice-versa.
- hiddenInsets = controller.hasZeroInsetsIme()
+ hiddenInsets = hasZeroInsetsIme
? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right,
mFloatingImeBottomInset)
: hiddenInsets;
@@ -423,7 +422,7 @@
Insets end = mShow
? controller.getShownStateInsets()
: hiddenInsets;
- Interpolator insetsInterpolator = getInsetsInterpolator();
+ Interpolator insetsInterpolator = controller.getInsetsInterpolator();
Interpolator alphaInterpolator = getAlphaInterpolator();
mAnimator.addUpdateListener(animation -> {
float rawFraction = animation.getAnimatedFraction();
@@ -486,9 +485,10 @@
}
}
- protected Interpolator getInsetsInterpolator() {
+ @Override
+ public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks) {
+ if (mHasAnimationCallbacks && hasZeroInsetsIme) {
return SYNC_IME_INTERPOLATOR;
} else if (mShow) {
return LINEAR_OUT_SLOW_IN_INTERPOLATOR;
@@ -507,10 +507,9 @@
Interpolator getAlphaInterpolator() {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks) {
+ if (mHasAnimationCallbacks && !mController.hasZeroInsetsIme()) {
return input -> 1f;
} else if (mShow) {
-
// Alpha animation takes half the time with linear interpolation;
return input -> Math.min(1f, 2 * input);
} else {
@@ -534,16 +533,10 @@
if (DEBUG) Log.d(TAG, "onAnimationFinish showOnFinish: " + mShow);
}
- /**
- * To get the animation duration in MS.
- */
- public long getDurationMs() {
- return mDurationMs;
- }
-
- private long calculateDurationMs() {
+ @Override
+ public long getDurationMs(boolean hasZeroInsetsIme) {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks) {
+ if (mHasAnimationCallbacks && hasZeroInsetsIme) {
return ANIMATION_DURATION_SYNC_IME_MS;
} else {
return ANIMATION_DURATION_UNSYNC_IME_MS;
@@ -593,13 +586,13 @@
private static class PendingControlRequest {
PendingControlRequest(@InsetsType int types, WindowInsetsAnimationControlListener listener,
- long durationMs, Interpolator interpolator, @AnimationType int animationType,
+ InsetsAnimationSpec insetsAnimationSpec,
+ @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CancellationSignal cancellationSignal, boolean useInsetsAnimationThread) {
this.types = types;
this.listener = listener;
- this.durationMs = durationMs;
- this.interpolator = interpolator;
+ this.mInsetsAnimationSpec = insetsAnimationSpec;
this.animationType = animationType;
this.layoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
this.cancellationSignal = cancellationSignal;
@@ -608,8 +601,7 @@
@InsetsType int types;
final WindowInsetsAnimationControlListener listener;
- final long durationMs;
- final Interpolator interpolator;
+ final InsetsAnimationSpec mInsetsAnimationSpec;
final @AnimationType int animationType;
final @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation;
final CancellationSignal cancellationSignal;
@@ -1201,12 +1193,10 @@
// We are about to playing the default animation. Passing a null frame indicates the
// controlled types should be animated regardless of the frame.
- controlAnimationUnchecked(
- pendingRequest.types, pendingRequest.cancellationSignal,
- pendingRequest.listener, null /* frame */,
- true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
- pendingRequest.animationType,
- pendingRequest.layoutInsetsDuringAnimation,
+ controlAnimationUnchecked(pendingRequest.types, pendingRequest.cancellationSignal,
+ pendingRequest.listener, null /* frame */, true /* fromIme */,
+ pendingRequest.mInsetsAnimationSpec,
+ pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation,
pendingRequest.useInsetsAnimationThread, statsToken);
}
@@ -1327,18 +1317,26 @@
mHost.getInputMethodManager(), null /* icProto */);
}
+ InsetsAnimationSpec spec = new InsetsAnimationSpec() {
+ @Override
+ public long getDurationMs(boolean hasZeroInsetsIme) {
+ return durationMs;
+ }
+ @Override
+ public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
+ return interpolator;
+ }
+ };
// TODO(b/342111149): Create statsToken here once ImeTracker#onStart becomes async.
- controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
- interpolator, animationType,
- getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
+ controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, spec,
+ animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
false /* useInsetsAnimationThread */, null);
}
private void controlAnimationUnchecked(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
- long durationMs, Interpolator interpolator,
- @AnimationType int animationType,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -1349,7 +1347,7 @@
// However, we might reject the request in some cases, such as delaying showing IME or
// rejecting showing IME.
controlAnimationUncheckedInner(types, cancellationSignal, listener, frame, fromIme,
- durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+ insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
useInsetsAnimationThread, statsToken);
// We are finishing setting the requested visible types. Report them to the server
@@ -1360,8 +1358,7 @@
private void controlAnimationUncheckedInner(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
- long durationMs, Interpolator interpolator,
- @AnimationType int animationType,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
if ((types & mTypesBeingCancelled) != 0) {
@@ -1418,8 +1415,8 @@
// TODO (b/323319146) remove layoutInsetsDuringAnimation from
// PendingControlRequest, as it is now only used for showing
final PendingControlRequest request = new PendingControlRequest(types,
- listener, durationMs,
- interpolator, animationType, LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+ listener, insetsAnimationSpec, animationType,
+ LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
cancellationSignal, false /* useInsetsAnimationThread */);
mPendingImeControlRequest = request;
// only add a timeout when the control is not currently showing
@@ -1460,11 +1457,9 @@
if (!imeReady) {
// IME isn't ready, all requested types will be animated once IME is ready
abortPendingImeControlRequest();
- final PendingControlRequest request = new PendingControlRequest(types,
- listener, durationMs,
- interpolator, animationType, layoutInsetsDuringAnimation,
- cancellationSignal,
- useInsetsAnimationThread);
+ final PendingControlRequest request = new PendingControlRequest(types, listener,
+ insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+ cancellationSignal, useInsetsAnimationThread);
mPendingImeControlRequest = request;
mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS);
if (DEBUG) Log.d(TAG, "Ime not ready. Create pending request");
@@ -1520,11 +1515,11 @@
final InsetsAnimationControlRunner runner = useInsetsAnimationThread
? new InsetsAnimationThreadControlRunner(controls,
- frame, mState, listener, typesReady, this, durationMs, interpolator,
- animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
- mHost.getHandler(), statsToken)
+ frame, mState, listener, typesReady, this,
+ insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+ mHost.getTranslator(), mHost.getHandler(), statsToken)
: new InsetsAnimationControlImpl(controls,
- frame, mState, listener, typesReady, this, durationMs, interpolator,
+ frame, mState, listener, typesReady, this, insetsAnimationSpec,
animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
statsToken);
if ((typesReady & WindowInsets.Type.ime()) != 0) {
@@ -2023,7 +2018,7 @@
// the controlled types should be animated regardless of the frame.
controlAnimationUnchecked(
types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
- listener.getDurationMs(), listener.getInsetsInterpolator(),
+ listener /* insetsAnimationSpec */,
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
!hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index 6e62221..f90b841 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -233,6 +233,16 @@
}
@Override
+ public long getDurationMs() {
+ return 0;
+ }
+
+ @Override
+ public Interpolator getInsetsInterpolator() {
+ return null;
+ }
+
+ @Override
public void setReadyDispatched(boolean dispatched) {
}
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 6578e9b..d3ea982 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -23,6 +23,7 @@
import android.graphics.Insets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
+import android.view.animation.Interpolator;
/**
* Controller for app-driven animation of system windows.
@@ -188,4 +189,16 @@
* fullscreen or non-overlapping).
*/
boolean hasZeroInsetsIme();
+
+ /**
+ * @hide
+ * @return The duration of the animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+ */
+ long getDurationMs();
+
+ /**
+ * @hide
+ * @return The interpolator of the animation.
+ */
+ Interpolator getInsetsInterpolator();
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index a5ba294..fe6aafb 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -982,6 +982,7 @@
private long mParentNodeId = UNDEFINED_NODE_ID;
private long mLabelForId = UNDEFINED_NODE_ID;
private long mLabeledById = UNDEFINED_NODE_ID;
+ private LongArray mLabeledByIds;
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
@@ -3599,6 +3600,133 @@
}
/**
+ * Adds the view which serves as the label of the view represented by
+ * this info for accessibility purposes. When multiple labels are
+ * added, the content from each label is combined in the order that
+ * they are added.
+ * <p>
+ * If visible text can be used to describe or give meaning to this UI,
+ * this method is preferred. For example, a TextView before an EditText
+ * in the UI usually specifies what information is contained in the
+ * EditText. Hence, the EditText is labeled by the TextView.
+ * </p>
+ *
+ * @param label A view that labels this node's source.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public void addLabeledBy(@NonNull View label) {
+ addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Adds the view which serves as the label of the view represented by
+ * this info for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the label. When multiple
+ * labels are added, the content from each label is combined in the order
+ * that they are added.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * If visible text can be used to describe or give meaning to this UI,
+ * this method is preferred. For example, a TextView before an EditText
+ * in the UI usually specifies what information is contained in the
+ * EditText. Hence, the EditText is labeled by the TextView.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root A root whose virtual descendant labels this node's source.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
+ enforceNotSealed();
+ Preconditions.checkNotNull(root, "%s must not be null", root);
+ if (mLabeledByIds == null) {
+ mLabeledByIds = new LongArray();
+ }
+ mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
+ mLabeledByIds.add(mLabeledById);
+ }
+
+ /**
+ * Gets the list of node infos which serve as the labels of the view represented by
+ * this info for accessibility purposes.
+ *
+ * @return The list of labels in the order that they were added.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
+ enforceSealed();
+ List<AccessibilityNodeInfo> labels = new ArrayList<>();
+ if (mLabeledByIds == null) {
+ return labels;
+ }
+ for (int i = 0; i < mLabeledByIds.size(); i++) {
+ labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
+ }
+ return labels;
+ }
+
+ /**
+ * Removes a label. If the label was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param label The node which serves as this node's label.
+ * @return true if the label was present
+ * @see #addLabeledBy(View)
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public boolean removeLabeledBy(@NonNull View label) {
+ return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Removes a label which is a virtual descendant of the given
+ * <code>root</code>. If <code>virtualDescendantId</code> is
+ * {@link View#NO_ID} the root is set as the label. If the label
+ * was not previously added to the node, calling this method has
+ * no effect.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual node which serves as this node's label.
+ * @return true if the label was present
+ * @see #addLabeledBy(View, int)
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final LongArray labeledByIds = mLabeledByIds;
+ if (labeledByIds == null) {
+ return false;
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ if (mLabeledById == labeledById) {
+ mLabeledById = UNDEFINED_NODE_ID;
+ }
+ final int index = labeledByIds.indexOf(labeledById);
+ if (index < 0) {
+ return false;
+ }
+ labeledByIds.remove(index);
+ return true;
+ }
+
+ /**
* Sets the view which serves as the label of the view represented by
* this info for accessibility purposes.
*
@@ -3631,7 +3759,17 @@
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ if (Flags.supportMultipleLabeledby()) {
+ if (mLabeledByIds == null) {
+ mLabeledByIds = new LongArray();
+ } else {
+ mLabeledByIds.clear();
+ }
+ }
mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ if (Flags.supportMultipleLabeledby()) {
+ mLabeledByIds.add(mLabeledById);
+ }
}
/**
@@ -4242,6 +4380,10 @@
fieldIndex++;
if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
+ if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4383,6 +4525,18 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final LongArray labeledByIds = mLabeledByIds;
+ if (labeledByIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int labeledByIdsSize = labeledByIds.size();
+ parcel.writeInt(labeledByIdsSize);
+ for (int i = 0; i < labeledByIdsSize; i++) {
+ parcel.writeLong(labeledByIds.get(i));
+ }
+ }
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4550,6 +4704,7 @@
mParentNodeId = other.mParentNodeId;
mLabelForId = other.mLabelForId;
mLabeledById = other.mLabeledById;
+ mLabeledByIds = other.mLabeledByIds;
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4656,6 +4811,18 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int labeledByIdsSize = parcel.readInt();
+ if (labeledByIdsSize <= 0) {
+ mLabeledByIds = null;
+ } else {
+ mLabeledByIds = new LongArray(labeledByIdsSize);
+ for (int i = 0; i < labeledByIdsSize; i++) {
+ final long labeledById = parcel.readLong();
+ mLabeledByIds.add(labeledById);
+ }
+ }
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index b281015..23d7732 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1365,10 +1365,10 @@
ImeTracker.PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY);
if (visible) {
insetsController.show(WindowInsets.Type.ime(),
- false /* fromIme */, null /* statsToken */);
+ false /* fromIme */, statsToken);
} else {
insetsController.hide(WindowInsets.Type.ime(),
- false /* fromIme */, null /* statsToken */);
+ false /* fromIme */, statsToken);
}
}
} else {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index fe26510..7366b9a 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -497,25 +497,32 @@
public abstract boolean getUseWebViewBackgroundForOverscrollBackground();
/**
- * Sets whether the WebView should save form data. In Android O, the
- * platform has implemented a fully functional Autofill feature to store
- * form data. Therefore, the Webview form data save feature is disabled.
+ * Sets whether the WebView should save form data. In {@link android.os.Build.VERSION_CODES#O},
+ * the platform has implemented a fully functional Autofill feature to store form data.
+ * Therefore, the Webview form data save feature is disabled.
*
- * Note that the feature will continue to be supported on older versions of
+ * <p>Note that the feature will continue to be supported on older versions of
* Android as before.
*
- * @deprecated In Android O and afterwards, this function does not have
- * any effect, the form data will be saved to platform's autofill service
- * if applicable.
+ * @see #getSaveFormData
+ * @deprecated In Android O and afterwards, this function does not have any effect. Form data
+ * will be saved to platform's autofill service if applicable.
*/
@Deprecated
public abstract void setSaveFormData(boolean save);
/**
- * Gets whether the WebView saves form data.
+ * Gets whether the WebView saves form data. In {@link android.os.Build.VERSION_CODES#O}, the
+ * platform has implemented a fully functional Autofill feature to store form data. Therefore,
+ * the Webview form data save feature is disabled.
+ *
+ * <p>Note that the feature will continue to be supported on older versions of
+ * Android as before.
*
* @return whether the WebView saves form data
* @see #setSaveFormData
+ * @deprecated In Android O and afterwards, this function does not have any effect. Form data
+ * will be filled from the platform's autofill service if applicable.
*/
@Deprecated
public abstract boolean getSaveFormData();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 72b268b..1ea20fa 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10627,7 +10627,7 @@
int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
if (mLayout.isLevelBoundary(startOffset)) {
- // TODO(b/247551937): Support gesture at level boundaries.
+ // Gesture at level boundaries is not supported.
return handleGestureFailure(gesture);
}
diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl
index b1d7582..a801776 100644
--- a/core/java/android/window/IBackAnimationRunner.aidl
+++ b/core/java/android/window/IBackAnimationRunner.aidl
@@ -38,14 +38,13 @@
/**
* Called when the system is ready for the handler to start animating all the visible tasks.
* @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
- windows to animate,
- * @param wallpapers The list of wallpapers to animate.
- * @param nonApps The list of non-app windows such as Bubbles to animate.
+ * windows to animate,
+ * @param prepareOpenTransition If non-null, the animation should start after receive open
+ * transition
* @param finishedCallback The callback to invoke when the animation is finished.
*/
void onAnimationStart(
in RemoteAnimationTarget[] apps,
- in RemoteAnimationTarget[] wallpapers,
- in RemoteAnimationTarget[] nonApps,
+ in IBinder prepareOpenTransition,
in IBackAnimationFinishedCallback finishedCallback) = 2;
}
\ No newline at end of file
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 4f84817..217bca7 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -225,3 +225,13 @@
description: "Adds a minimize button the the caption bar"
bug: "356843241"
}
+
+flag {
+ name: "skip_compat_ui_education_in_desktop_mode"
+ namespace: "lse_desktop_experience"
+ description: "Ignore Compat UI educations when in Desktop Mode."
+ bug: "357062954"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index be744fd..67fc270 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -19,16 +19,6 @@
}
flag {
- name: "do_not_skip_ime_by_target_visibility"
- namespace: "windowing_frontend"
- description: "Avoid window traversal missing IME"
- bug: "339375944"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "apply_lifecycle_on_pip_change"
namespace: "windowing_frontend"
description: "Make pip activity lifecyle change with windowing mode"
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 4ccdf79..cf3a54b 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -109,45 +109,13 @@
public static List<AccessibilityTarget> getInstalledTargets(Context context,
@UserShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
- targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
+ targets.addAll(getAccessibilityServiceTargets(context, shortcutType));
+ targets.addAll(getAccessibilityActivityTargets(context, shortcutType));
targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
return targets;
}
- private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
- @UserShortcutType int shortcutType) {
- final List<AccessibilityTarget> serviceTargets =
- getAccessibilityServiceTargets(context, shortcutType);
- final List<AccessibilityTarget> activityTargets =
- getAccessibilityActivityTargets(context, shortcutType);
-
- for (AccessibilityTarget activityTarget : activityTargets) {
- serviceTargets.removeIf(
- serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget));
- }
-
- final List<AccessibilityTarget> targets = new ArrayList<>();
- targets.addAll(serviceTargets);
- targets.addAll(activityTargets);
-
- return targets;
- }
-
- private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget,
- @NonNull AccessibilityTarget activityTarget) {
- final ComponentName serviceComponentName =
- ComponentName.unflattenFromString(serviceTarget.getId());
- final ComponentName activityComponentName =
- ComponentName.unflattenFromString(activityTarget.getId());
- final boolean isSamePackageName = activityComponentName.getPackageName().equals(
- serviceComponentName.getPackageName());
- final boolean isSameLabel = activityTarget.getLabel().equals(
- serviceTarget.getLabel());
-
- return isSamePackageName && isSameLabel;
- }
-
private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
@UserShortcutType int shortcutType) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
diff --git a/core/java/com/android/internal/protolog/IProtoLogClient.aidl b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
index 969ed99..64944f4 100644
--- a/core/java/com/android/internal/protolog/IProtoLogClient.aidl
+++ b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
@@ -20,7 +20,7 @@
* The ProtoLog client interface.
*
* These clients will communicate bi-directionally with the ProtoLog service
- * (@see IProtoLogService.aidl) running in the system process.
+ * (@see IProtoLogConfigurationService.aidl) running in the system process.
*
* {@hide}
*/
diff --git a/core/java/com/android/internal/protolog/IProtoLogService.aidl b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
similarity index 97%
rename from core/java/com/android/internal/protolog/IProtoLogService.aidl
rename to core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
index cc349ea..ce94828 100644
--- a/core/java/com/android/internal/protolog/IProtoLogService.aidl
+++ b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
@@ -40,7 +40,7 @@
*
* {@hide}
*/
-interface IProtoLogService {
+interface IProtoLogConfigurationService {
interface IRegisterClientArgs {
String[] getGroups();
boolean[] getGroupsDefaultLogcatStatus();
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index b82c660..34e0418 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -40,6 +40,8 @@
*/
@Deprecated
public class LogcatOnlyProtoLogImpl implements IProtoLog {
+ private static final String LOG_TAG = LogcatOnlyProtoLogImpl.class.getName();
+
@Override
public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
Object[] args) {
@@ -48,19 +50,21 @@
@Override
public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
- if (REQUIRE_PROTOLOGTOOL) {
- throw new RuntimeException(
- "REQUIRE_PROTOLOGTOOL not set to false before the first log call.");
+ if (REQUIRE_PROTOLOGTOOL && group.isLogToProto()) {
+ Log.w(LOG_TAG, "ProtoLog message not processed. Failed to log it to proto. "
+ + "Logging it below to logcat instead.");
}
- String formattedString = TextUtils.formatSimple(messageString, args);
- switch (logLevel) {
- case VERBOSE -> Log.v(group.getTag(), formattedString);
- case INFO -> Log.i(group.getTag(), formattedString);
- case DEBUG -> Log.d(group.getTag(), formattedString);
- case WARN -> Log.w(group.getTag(), formattedString);
- case ERROR -> Log.e(group.getTag(), formattedString);
- case WTF -> Log.wtf(group.getTag(), formattedString);
+ if (group.isLogToLogcat() || group.isLogToProto()) {
+ String formattedString = TextUtils.formatSimple(messageString, args);
+ switch (logLevel) {
+ case VERBOSE -> Log.v(group.getTag(), formattedString);
+ case INFO -> Log.i(group.getTag(), formattedString);
+ case DEBUG -> Log.d(group.getTag(), formattedString);
+ case WARN -> Log.w(group.getTag(), formattedString);
+ case ERROR -> Log.e(group.getTag(), formattedString);
+ case WTF -> Log.wtf(group.getTag(), formattedString);
+ }
}
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 5517967..b2fdf17 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -16,7 +16,7 @@
package com.android.internal.protolog;
-import static android.content.Context.PROTOLOG_SERVICE;
+import static android.content.Context.PROTOLOG_CONFIGURATION_SERVICE;
import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE;
import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS;
import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID;
@@ -114,7 +114,7 @@
private final Runnable mCacheUpdater;
@Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
- private final IProtoLogService mProtoLogService;
+ private final IProtoLogConfigurationService mProtoLogConfigurationService;
@NonNull
private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
@@ -186,30 +186,32 @@
registerGroupsLocally(groups);
if (android.tracing.Flags.clientSideProtoLogging()) {
- mProtoLogService =
- IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE));
- Objects.requireNonNull(mProtoLogService,
+ mProtoLogConfigurationService =
+ IProtoLogConfigurationService.Stub.asInterface(ServiceManager.getService(
+ PROTOLOG_CONFIGURATION_SERVICE));
+ Objects.requireNonNull(mProtoLogConfigurationService,
"ServiceManager returned a null ProtoLog service");
try {
- var args = new ProtoLogService.RegisterClientArgs();
+ var args = new ProtoLogConfigurationService.RegisterClientArgs();
if (viewerConfigFilePath != null) {
args.setViewerConfigFile(viewerConfigFilePath);
}
final var groupArgs = Stream.of(groups)
- .map(group -> new ProtoLogService.RegisterClientArgs.GroupConfig(
- group.name(), group.isLogToLogcat()))
- .toArray(ProtoLogService.RegisterClientArgs.GroupConfig[]::new);
+ .map(group -> new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(group.name(), group.isLogToLogcat()))
+ .toArray(
+ ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new);
args.setGroups(groupArgs);
- mProtoLogService.registerClient(this, args);
+ mProtoLogConfigurationService.registerClient(this, args);
} catch (RemoteException e) {
throw new RuntimeException("Failed to register ProtoLog client");
}
} else {
- mProtoLogService = null;
+ mProtoLogConfigurationService = null;
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 660d3c9..bf77db7 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -63,6 +63,9 @@
* @param groups The ProtoLog groups that will be used in the process.
*/
public static void init(IProtoLogGroup... groups) {
+ // These tracing instances are only used when we cannot or do not preprocess the source
+ // files to extract out the log strings. Otherwise, the trace calls are replaced with calls
+ // directly to the generated tracing implementations.
if (android.tracing.Flags.perfettoProtologTracing()) {
synchronized (sInitLock) {
if (sProtoLogInstance != null) {
@@ -76,8 +79,6 @@
sProtoLogInstance = new PerfettoProtoLogImpl(groups);
}
} else {
- // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
- // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
sProtoLogInstance = new LogcatOnlyProtoLogImpl();
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
index 3dab2e3..82d8d34 100644
--- a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
+++ b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
@@ -29,18 +29,20 @@
public class ProtoLogCommandHandler extends ShellCommand {
@NonNull
- private final ProtoLogService mProtoLogService;
+ private final ProtoLogConfigurationService mProtoLogConfigurationService;
@Nullable
private final PrintWriter mPrintWriter;
- public ProtoLogCommandHandler(@NonNull ProtoLogService protoLogService) {
- this(protoLogService, null);
+ public ProtoLogCommandHandler(
+ @NonNull ProtoLogConfigurationService protoLogConfigurationService) {
+ this(protoLogConfigurationService, null);
}
@VisibleForTesting
public ProtoLogCommandHandler(
- @NonNull ProtoLogService protoLogService, @Nullable PrintWriter printWriter) {
- this.mProtoLogService = protoLogService;
+ @NonNull ProtoLogConfigurationService protoLogConfigurationService,
+ @Nullable PrintWriter printWriter) {
+ this.mProtoLogConfigurationService = protoLogConfigurationService;
this.mPrintWriter = printWriter;
}
@@ -94,7 +96,7 @@
switch (cmd) {
case "list": {
- final String[] availableGroups = mProtoLogService.getGroups();
+ final String[] availableGroups = mProtoLogConfigurationService.getGroups();
if (availableGroups.length == 0) {
pw.println("No ProtoLog groups registered with ProtoLog service.");
return 0;
@@ -117,12 +119,13 @@
pw.println("ProtoLog group " + group + "'s status:");
- if (!Set.of(mProtoLogService.getGroups()).contains(group)) {
+ if (!Set.of(mProtoLogConfigurationService.getGroups()).contains(group)) {
pw.println("UNREGISTERED");
return 0;
}
- pw.println("LOG_TO_LOGCAT = " + mProtoLogService.isLoggingToLogcat(group));
+ pw.println("LOG_TO_LOGCAT = "
+ + mProtoLogConfigurationService.isLoggingToLogcat(group));
return 0;
}
default: {
@@ -142,11 +145,11 @@
switch (cmd) {
case "enable" -> {
- mProtoLogService.enableProtoLogToLogcat(processGroups());
+ mProtoLogConfigurationService.enableProtoLogToLogcat(processGroups());
return 0;
}
case "disable" -> {
- mProtoLogService.disableProtoLogToLogcat(processGroups());
+ mProtoLogConfigurationService.disableProtoLogToLogcat(processGroups());
return 0;
}
default -> {
@@ -159,7 +162,7 @@
@NonNull
private String[] processGroups() {
if (getRemainingArgsCount() == 0) {
- return mProtoLogService.getGroups();
+ return mProtoLogConfigurationService.getGroups();
}
final List<String> groups = new ArrayList<>();
diff --git a/core/java/com/android/internal/protolog/ProtoLogService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
similarity index 97%
rename from core/java/com/android/internal/protolog/ProtoLogService.java
rename to core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index 2333a06..1765738 100644
--- a/core/java/com/android/internal/protolog/ProtoLogService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -70,9 +70,9 @@
* <p>
* This service is intended to run on the system server, such that it never gets frozen.
*/
-@SystemService(Context.PROTOLOG_SERVICE)
-public final class ProtoLogService extends IProtoLogService.Stub {
- private static final String LOG_TAG = "ProtoLogService";
+@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
+public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub {
+ private static final String LOG_TAG = "ProtoLogConfigurationService";
private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
this::onTracingInstanceStart,
@@ -114,12 +114,12 @@
private final ViewerConfigFileTracer mViewerConfigFileTracer;
- public ProtoLogService() {
- this(ProtoLogService::dumpTransitionTraceConfig);
+ public ProtoLogConfigurationService() {
+ this(ProtoLogConfigurationService::dumpTransitionTraceConfig);
}
@VisibleForTesting
- public ProtoLogService(@NonNull ViewerConfigFileTracer tracer) {
+ public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) {
// Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
// receive the lifecycle callbacks of the datasource and write the viewer configs if and
// when required to the datasource.
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 6dc6585..5c06b87 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -17,6 +17,7 @@
package com.android.internal.protolog;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.DEFAULT;
+import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.DEFAULT_LOG_FROM_LEVEL;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.ENABLE_ALL;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.GROUP_OVERRIDES;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.TRACING_MODE;
@@ -43,7 +44,6 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
ProtoLogDataSource.TlsState,
@@ -190,73 +190,54 @@
final Map<String, GroupConfig> groupConfigs = new HashMap<>();
while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (configStream.getFieldNumber() == (int) TRACING_MODE) {
- int tracingMode = configStream.readInt(TRACING_MODE);
- switch (tracingMode) {
- case DEFAULT:
- break;
- case ENABLE_ALL:
- defaultLogFromLevel = LogLevel.DEBUG;
- break;
- default:
- throw new RuntimeException("Unhandled ProtoLog tracing mode type");
- }
- }
- if (configStream.getFieldNumber() == (int) GROUP_OVERRIDES) {
- final long group_overrides_token = configStream.start(GROUP_OVERRIDES);
-
- String tag = null;
- LogLevel logFromLevel = defaultLogFromLevel;
- boolean collectStackTrace = false;
- while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (configStream.getFieldNumber() == (int) GROUP_NAME) {
- tag = configStream.readString(GROUP_NAME);
+ switch (configStream.getFieldNumber()) {
+ case (int) DEFAULT_LOG_FROM_LEVEL:
+ int defaultLogFromLevelInt = configStream.readInt(DEFAULT_LOG_FROM_LEVEL);
+ if (defaultLogFromLevelInt < defaultLogFromLevel.ordinal()) {
+ defaultLogFromLevel =
+ logLevelFromInt(configStream.readInt(DEFAULT_LOG_FROM_LEVEL));
}
- if (configStream.getFieldNumber() == (int) LOG_FROM) {
- final int logFromInt = configStream.readInt(LOG_FROM);
- switch (logFromInt) {
- case (ProtologCommon.PROTOLOG_LEVEL_DEBUG): {
- logFromLevel = LogLevel.DEBUG;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE): {
- logFromLevel = LogLevel.VERBOSE;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_INFO): {
- logFromLevel = LogLevel.INFO;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_WARN): {
- logFromLevel = LogLevel.WARN;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_ERROR): {
- logFromLevel = LogLevel.ERROR;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_WTF): {
- logFromLevel = LogLevel.WTF;
- break;
- }
- default: {
- throw new RuntimeException("Unhandled log level");
- }
+ break;
+ case (int) TRACING_MODE:
+ int tracingMode = configStream.readInt(TRACING_MODE);
+ switch (tracingMode) {
+ case DEFAULT:
+ break;
+ case ENABLE_ALL:
+ defaultLogFromLevel = LogLevel.DEBUG;
+ break;
+ default:
+ throw new RuntimeException("Unhandled ProtoLog tracing mode type");
+ }
+ break;
+ case (int) GROUP_OVERRIDES:
+ final long group_overrides_token = configStream.start(GROUP_OVERRIDES);
+
+ String tag = null;
+ LogLevel logFromLevel = defaultLogFromLevel;
+ boolean collectStackTrace = false;
+ while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.getFieldNumber() == (int) GROUP_NAME) {
+ tag = configStream.readString(GROUP_NAME);
+ }
+ if (configStream.getFieldNumber() == (int) LOG_FROM) {
+ final int logFromInt = configStream.readInt(LOG_FROM);
+ logFromLevel = logLevelFromInt(logFromInt);
+ }
+ if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
+ collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
}
}
- if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
- collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
+
+ if (tag == null) {
+ throw new RuntimeException("Failed to decode proto config. "
+ + "Got a group override without a group tag.");
}
- }
- if (tag == null) {
- throw new RuntimeException("Failed to decode proto config. "
- + "Got a group override without a group tag.");
- }
+ groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
- groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
-
- configStream.end(group_overrides_token);
+ configStream.end(group_overrides_token);
+ break;
}
}
@@ -265,6 +246,18 @@
return new ProtoLogConfig(defaultLogFromLevel, groupConfigs);
}
+ private LogLevel logLevelFromInt(int logFromInt) {
+ return switch (logFromInt) {
+ case (ProtologCommon.PROTOLOG_LEVEL_DEBUG) -> LogLevel.DEBUG;
+ case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE) -> LogLevel.VERBOSE;
+ case (ProtologCommon.PROTOLOG_LEVEL_INFO) -> LogLevel.INFO;
+ case (ProtologCommon.PROTOLOG_LEVEL_WARN) -> LogLevel.WARN;
+ case (ProtologCommon.PROTOLOG_LEVEL_ERROR) -> LogLevel.ERROR;
+ case (ProtologCommon.PROTOLOG_LEVEL_WTF) -> LogLevel.WTF;
+ default -> throw new RuntimeException("Unhandled log level");
+ };
+ }
+
public static class Instance extends DataSourceInstance {
public interface TracingInstanceStartCallback {
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 76ce452..f8a1436 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -16,6 +16,7 @@
package com.android.internal.statusbar;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,7 +24,18 @@
import android.text.TextUtils;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+/**
+ * Representation of an icon that should appear in the status bar.
+ *
+ * <p>This includes notifications, conversations, and icons displayed on the right side (e.g.
+ * Wifi, Vibration/Silence, Priority Modes, etc).
+ *
+ * <p>This class is {@link Parcelable} but the {@link #preloadedIcon} is not (and will be lost if
+ * the object is copied through parcelling). If {@link #preloadedIcon} is supplied, it must match
+ * the {@link #icon} resource/bitmap.
+ */
public class StatusBarIcon implements Parcelable {
public enum Type {
// Notification: the sender avatar for important conversations
@@ -34,7 +46,9 @@
// Notification: the small icon from the notification
NotifSmallIcon,
// The wi-fi, cellular or battery icon.
- SystemIcon
+ SystemIcon,
+ // Some other icon, corresponding to a resource (possibly in a different package).
+ ResourceIcon
}
public UserHandle user;
@@ -46,6 +60,13 @@
public CharSequence contentDescription;
public Type type;
+ /**
+ * Optional {@link Drawable} corresponding to {@link #icon}. This field is not parcelable, so
+ * will be lost if the object is sent to a different process. If you set it, make sure to
+ * <em>also</em> set {@link #icon} pointing to the corresponding resource.
+ */
+ @Nullable public Drawable preloadedIcon;
+
public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number,
CharSequence contentDescription, Type type) {
if (icon.getType() == Icon.TYPE_RESOURCE
@@ -88,6 +109,7 @@
StatusBarIcon that = new StatusBarIcon(this.user, this.pkg, this.icon,
this.iconLevel, this.number, this.contentDescription, this.type);
that.visible = this.visible;
+ that.preloadedIcon = this.preloadedIcon;
return that;
}
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 50832a5..8dd63cc 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -256,9 +256,21 @@
}
}
-static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
- jint texName, jboolean singleBufferMode, jobject weakThiz)
-{
+static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, jint texName,
+ jboolean singleBufferMode, jobject weakThiz) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<SurfaceTexture> surfaceTexture;
+ if (isDetached) {
+ surfaceTexture = new SurfaceTexture(GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+ } else {
+ surfaceTexture =
+ new SurfaceTexture(texName, GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+ }
+
+ if (singleBufferMode) {
+ surfaceTexture->setMaxBufferCount(1);
+ }
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -275,6 +287,7 @@
surfaceTexture = new SurfaceTexture(consumer, texName,
GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
if (surfaceTexture == 0) {
jniThrowException(env, OutOfResourcesException,
@@ -287,11 +300,27 @@
createProcessUniqueId()));
// If the current context is protected, inform the producer.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ surfaceTexture->setConsumerIsProtected(isProtectedContext());
+
+ SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
+ sp<Surface> surface = surfaceTexture->getSurface();
+ if (nullptr == surface) {
+ jniThrowException(env, IllegalStateException, "Unable to get surface from SurfaceTexture");
+ return;
+ }
+ sp<IGraphicBufferProducer> igbp = surface->getIGraphicBufferProducer();
+ if (nullptr == igbp) {
+ jniThrowException(env, IllegalStateException, "Unable to get IGBP from Surface");
+ return;
+ }
+ SurfaceTexture_setProducer(env, thiz, igbp);
+#else
consumer->setConsumerIsProtected(isProtectedContext());
SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
SurfaceTexture_setProducer(env, thiz, producer);
-
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
jniThrowRuntimeException(env,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2dd560c..91c3370 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3836,7 +3836,6 @@
<!-- Allows an application to use audit logging API.
@hide
@SystemApi
- @FlaggedApi("android.app.admin.flags.security_log_v2_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"
android:protectionLevel="internal|role" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a52bd4..cf0fc61 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5466,6 +5466,8 @@
<!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
<java-symbol type="xml" name="haptic_feedback_customization" />
+ <java-symbol type="xml" name="haptic_feedback_customization_source_rotary_encoder" />
+ <java-symbol type="xml" name="haptic_feedback_customization_source_touchscreen" />
<!-- For ActivityManager PSS profiling configurability -->
<java-symbol type="bool" name="config_am_disablePssProfiling" />
diff --git a/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
new file mode 100644
index 0000000..7ac0787
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<haptic-feedback-constants/>
diff --git a/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
new file mode 100644
index 0000000..7ac0787
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<haptic-feedback-constants/>
diff --git a/core/tests/coretests/src/android/util/StateSetTest.java b/core/tests/coretests/src/android/util/StateSetTest.java
index 14e4e20..c9df83d 100644
--- a/core/tests/coretests/src/android/util/StateSetTest.java
+++ b/core/tests/coretests/src/android/util/StateSetTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -33,7 +32,6 @@
* Tests for {@link StateSet}
*/
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = StateSet.class)
public class StateSetTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 668487d..786f1e8 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -40,6 +40,7 @@
import android.util.SparseArray;
import android.view.SurfaceControl.Transaction;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -117,11 +118,21 @@
SparseArray<InsetsSourceControl> controls = new SparseArray<>();
controls.put(ID_STATUS_BAR, topConsumer.getControl());
controls.put(ID_NAVIGATION_BAR, navConsumer.getControl());
+ InsetsAnimationSpec spec = new InsetsAnimationSpec() {
+ @Override
+ public long getDurationMs(boolean hasZeroInsetsIme) {
+ return 10;
+ }
+ @Override
+ public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
+ return new LinearInterpolator();
+ }
+ };
+
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- mMockController, 10 /* durationMs */, new LinearInterpolator(),
- 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
- null /* statsToken */);
+ mMockController, spec /* insetsAnimationSpecCreator */, 0 /* animationType */,
+ 0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */);
mController.setReadyDispatched(true);
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index a5137bdf..6e563ff 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 43;
+ private static final int NUM_MARSHALLED_PROPERTIES = 44;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
index a895378..b183ecb 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.os.Parcel;
import android.os.UserHandle;
@@ -37,18 +39,55 @@
*/
@Test
public void testParcelable() {
+ final StatusBarIcon original = newStatusBarIcon();
+
+ final StatusBarIcon copy = parcelAndUnparcel(original);
+
+ assertSerializableFieldsEqual(copy, original);
+ }
+
+ @Test
+ public void testClone_withPreloaded() {
+ final StatusBarIcon original = newStatusBarIcon();
+ original.preloadedIcon = new ColorDrawable(Color.RED);
+
+ final StatusBarIcon copy = original.clone();
+
+ assertSerializableFieldsEqual(copy, original);
+ assertThat(copy.preloadedIcon).isNotNull();
+ assertThat(copy.preloadedIcon).isInstanceOf(ColorDrawable.class);
+ assertThat(((ColorDrawable) copy.preloadedIcon).getColor()).isEqualTo(Color.RED);
+ }
+
+ @Test
+ public void testClone_noPreloaded() {
+ final StatusBarIcon original = newStatusBarIcon();
+
+ final StatusBarIcon copy = original.clone();
+
+ assertSerializableFieldsEqual(copy, original);
+ assertThat(copy.preloadedIcon).isEqualTo(original.preloadedIcon);
+ }
+
+
+ private static StatusBarIcon newStatusBarIcon() {
final UserHandle dummyUserHandle = UserHandle.of(100);
final String dummyIconPackageName = "com.android.internal.statusbar.test";
final int dummyIconId = 123;
final int dummyIconLevel = 1;
final int dummyIconNumber = 2;
final CharSequence dummyIconContentDescription = "dummyIcon";
- final StatusBarIcon original = new StatusBarIcon(dummyIconPackageName, dummyUserHandle,
- dummyIconId, dummyIconLevel, dummyIconNumber, dummyIconContentDescription,
+ return new StatusBarIcon(
+ dummyIconPackageName,
+ dummyUserHandle,
+ dummyIconId,
+ dummyIconLevel,
+ dummyIconNumber,
+ dummyIconContentDescription,
StatusBarIcon.Type.SystemIcon);
+ }
- final StatusBarIcon copy = clone(original);
-
+ private static void assertSerializableFieldsEqual(StatusBarIcon copy, StatusBarIcon original) {
assertThat(copy.user).isEqualTo(original.user);
assertThat(copy.pkg).isEqualTo(original.pkg);
assertThat(copy.icon.sameAs(original.icon)).isTrue();
@@ -56,19 +95,17 @@
assertThat(copy.visible).isEqualTo(original.visible);
assertThat(copy.number).isEqualTo(original.number);
assertThat(copy.contentDescription).isEqualTo(original.contentDescription);
+ assertThat(copy.type).isEqualTo(original.type);
}
- private StatusBarIcon clone(StatusBarIcon original) {
- Parcel parcel = null;
+ private static StatusBarIcon parcelAndUnparcel(StatusBarIcon original) {
+ Parcel parcel = Parcel.obtain();
try {
- parcel = Parcel.obtain();
original.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return StatusBarIcon.CREATOR.createFromParcel(parcel);
} finally {
- if (parcel != null) {
- parcel.recycle();
- }
+ parcel.recycle();
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d7da051..27194b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -1066,14 +1066,30 @@
return true;
}
+ private void kickStartAnimation() {
+ startSystemAnimation();
+
+ // Dispatch the first progress after animation start for
+ // smoothing the initial animation, instead of waiting for next
+ // onMove.
+ final BackMotionEvent backFinish = mCurrentTracker
+ .createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ if (!mBackGestureStarted) {
+ // if the down -> up gesture happened before animation
+ // start, we have to trigger the uninterruptible transition
+ // to finish the back animation.
+ startPostCommitAnimation();
+ }
+ }
+
private void createAdapter() {
IBackAnimationRunner runner =
new IBackAnimationRunner.Stub() {
@Override
public void onAnimationStart(
RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
+ IBinder token,
IBackAnimationFinishedCallback finishedCallback) {
mShellExecutor.execute(
() -> {
@@ -1085,21 +1101,12 @@
}
mBackAnimationFinishedCallback = finishedCallback;
mApps = apps;
- startSystemAnimation();
- mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
-
- // Dispatch the first progress after animation start for
- // smoothing the initial animation, instead of waiting for next
- // onMove.
- final BackMotionEvent backFinish = mCurrentTracker
- .createProgressEvent();
- dispatchOnBackProgressed(mActiveCallback, backFinish);
- if (!mBackGestureStarted) {
- // if the down -> up gesture happened before animation
- // start, we have to trigger the uninterruptible transition
- // to finish the back animation.
- startPostCommitAnimation();
+ // app only visible after transition ready, break for now.
+ if (token != null) {
+ return;
}
+ kickStartAnimation();
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
});
}
@@ -1199,6 +1206,9 @@
@NonNull SurfaceControl.Transaction st,
@NonNull SurfaceControl.Transaction ft,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ kickStartAnimation();
+ }
// Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
// need to post to ShellExecutor when called.
if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index c2ee223..972b78f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -39,6 +39,7 @@
import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener;
@@ -67,6 +68,7 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.IntPredicate;
import java.util.function.Predicate;
/**
@@ -189,6 +191,9 @@
@NonNull
private final CompatUIStatusManager mCompatUIStatusManager;
+ @NonNull
+ private final IntPredicate mInDesktopModePredicate;
+
public CompatUIController(@NonNull Context context,
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -202,7 +207,8 @@
@NonNull CompatUIConfiguration compatUIConfiguration,
@NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
@NonNull AccessibilityManager accessibilityManager,
- @NonNull CompatUIStatusManager compatUIStatusManager) {
+ @NonNull CompatUIStatusManager compatUIStatusManager,
+ @NonNull IntPredicate isDesktopModeEnablePredicate) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -218,6 +224,7 @@
mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
DISAPPEAR_DELAY_MS, flags);
mCompatUIStatusManager = compatUIStatusManager;
+ mInDesktopModePredicate = isDesktopModeEnablePredicate;
shellInit.addInitCallback(this::onInit, this);
}
@@ -251,7 +258,9 @@
updateActiveTaskInfo(taskInfo);
}
- if (taskInfo.configuration == null || taskListener == null) {
+ // We close all the Compat UI educations in case we're in desktop mode.
+ if (taskInfo.configuration == null || taskListener == null
+ || isInDesktopMode(taskInfo.displayId)) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
return;
@@ -350,7 +359,6 @@
mOnInsetsChangedListeners.remove(displayId);
}
-
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
updateDisplayLayout(displayId);
@@ -692,7 +700,8 @@
mContext.startActivityAsUser(intent, userHandle);
}
- private void removeLayouts(int taskId) {
+ @VisibleForTesting
+ void removeLayouts(int taskId) {
final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId);
if (compatLayout != null) {
compatLayout.release();
@@ -825,4 +834,9 @@
boolean mHasShownCameraCompatHint;
boolean mHasShownUserAspectRatioSettingsButtonHint;
}
+
+ private boolean isInDesktopMode(int displayId) {
+ return Flags.skipCompatUiEducationInDesktopMode()
+ && mInDesktopModePredicate.test(displayId);
+ }
}
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 98536bf..42937c1 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
@@ -137,6 +137,7 @@
import dagger.Provides;
import java.util.Optional;
+import java.util.function.IntPredicate;
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
@@ -261,6 +262,7 @@
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
Lazy<AccessibilityManager> accessibilityManager,
CompatUIRepository compatUIRepository,
+ Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
@NonNull CompatUIState compatUIState,
@NonNull CompatUIComponentIdGenerator componentIdGenerator,
@NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -273,6 +275,10 @@
new DefaultCompatUIHandler(compatUIRepository, compatUIState,
componentIdGenerator, compatUIComponentFactory, mainExecutor));
}
+ final IntPredicate inDesktopModePredicate =
+ desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
+ modeTaskRepository.getVisibleTaskCount(displayId) > 0)
+ .orElseGet(() -> displayId -> false);
return Optional.of(
new CompatUIController(
context,
@@ -288,7 +294,8 @@
compatUIConfiguration.get(),
compatUIShellCommandHandler.get(),
accessibilityManager.get(),
- compatUIStatusManager));
+ compatUIStatusManager,
+ inDesktopModePredicate));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 51ce2c6..3464fef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -42,6 +42,7 @@
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTaskListener;
import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.pip2.phone.PipTransitionState;
@@ -73,12 +74,13 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
PipTouchHandler pipTouchHandler,
+ PipTaskListener pipTaskListener,
@NonNull PipScheduler pipScheduler,
@NonNull PipTransitionState pipStackListenerController,
@NonNull PipUiStateChangeController pipUiStateChangeController) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
- pipStackListenerController, pipUiStateChangeController);
+ pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
+ pipScheduler, pipStackListenerController, pipUiStateChangeController);
}
@WMSingleton
@@ -123,9 +125,11 @@
@Provides
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
+ PhonePipMenuController pipMenuController,
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState) {
- return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
+ return new PipScheduler(context, pipBoundsState, pipMenuController,
+ mainExecutor, pipTransitionState);
}
@WMSingleton
@@ -190,4 +194,17 @@
PipTransitionState pipTransitionState) {
return new PipUiStateChangeController(pipTransitionState);
}
+
+ @WMSingleton
+ @Provides
+ static PipTaskListener providePipTaskListener(Context context,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
+ PipScheduler pipScheduler,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipTaskListener(context, shellTaskOrganizer, pipTransitionState,
+ pipScheduler, pipBoundsState, pipBoundsAlgorithm, mainExecutor);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 1a103d3..d72ec90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -22,13 +22,15 @@
import android.os.Bundle
import android.os.IBinder
import android.os.SystemClock
+import android.os.SystemProperties
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
-import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.InteractionJankMonitor
@@ -893,13 +895,10 @@
) {
private val positionSpringConfig =
- PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW,
- SpringForce.DAMPING_RATIO_LOW_BOUNCY
- )
+ PhysicsAnimator.SpringConfig(POSITION_SPRING_STIFFNESS, POSITION_SPRING_DAMPING_RATIO)
private val sizeSpringConfig =
- PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ PhysicsAnimator.SpringConfig(SIZE_SPRING_STIFFNESS, SIZE_SPRING_DAMPING_RATIO)
/**
* @return layers in order:
@@ -929,7 +928,7 @@
finishTransaction.hide(homeLeash)
// Setup freeform tasks before animation
state.freeformTaskChanges.forEach { change ->
- val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+ val startScale = FREEFORM_TASKS_INITIAL_SCALE
val startX =
change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2
val startY =
@@ -994,9 +993,22 @@
(animBounds.width() - startBounds.width()).toFloat() /
(endBounds.width() - startBounds.width())
val animScale = startScale + animFraction * (1 - startScale)
- // Freeform animation starts 50% in the animation
- val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f
- val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+ // Freeform animation starts with freeform animation offset relative to the commit
+ // animation and plays until the commit animation ends. For instance:
+ // - if the freeform animation offset is `0.0` the freeform tasks animate alongside
+ // - if the freeform animation offset is `0.6` the freeform tasks will
+ // start animating at 60% fraction of the commit animation and will complete when
+ // the commit animation fraction is 100%.
+ // - if the freeform animation offset is `1.0` then freeform tasks will appear
+ // without animation after commit animation finishes.
+ val freeformAnimFraction =
+ if (FREEFORM_TASKS_ANIM_OFFSET != 1f) {
+ max(animFraction - FREEFORM_TASKS_ANIM_OFFSET, 0f) /
+ (1f - FREEFORM_TASKS_ANIM_OFFSET)
+ } else {
+ 0f
+ }
+ val freeformStartScale = FREEFORM_TASKS_INITIAL_SCALE
val freeformAnimScale =
freeformStartScale + freeformAnimFraction * (1 - freeformStartScale)
tx.apply {
@@ -1032,10 +1044,53 @@
}
companion object {
+ /** The freeform tasks initial scale when committing the drag-to-desktop gesture. */
+ private val FREEFORM_TASKS_INITIAL_SCALE =
+ propertyValue("freeform_tasks_initial_scale", scale = 100f, default = 0.9f)
+
+ /** The freeform tasks animation offset relative to the whole animation duration. */
+ private val FREEFORM_TASKS_ANIM_OFFSET =
+ propertyValue("freeform_tasks_anim_offset", scale = 100f, default = 0.5f)
+
+ /** The spring force stiffness used to place the window into the final position. */
+ private val POSITION_SPRING_STIFFNESS =
+ propertyValue("position_stiffness", default = SpringForce.STIFFNESS_LOW)
+
+ /** The spring force damping ratio used to place the window into the final position. */
+ private val POSITION_SPRING_DAMPING_RATIO =
+ propertyValue(
+ "position_damping_ratio",
+ scale = 100f,
+ default = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ )
+
+ /** The spring force stiffness used to resize the window into the final bounds. */
+ private val SIZE_SPRING_STIFFNESS =
+ propertyValue("size_stiffness", default = SpringForce.STIFFNESS_LOW)
+
+ /** The spring force damping ratio used to resize the window into the final bounds. */
+ private val SIZE_SPRING_DAMPING_RATIO =
+ propertyValue(
+ "size_damping_ratio",
+ scale = 100f,
+ default = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ )
+
+ /** Drag to desktop transition system properties group. */
+ @VisibleForTesting
+ const val SYSTEM_PROPERTIES_GROUP = "persist.wm.debug.desktop_transitions.drag_to_desktop"
+
/**
- * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop
- * gesture.
+ * Drag to desktop transition system property value with [name].
+ *
+ * @param scale an optional scale to apply to the value read from the system property.
+ * @param default a default value to return if the system property isn't set.
*/
- private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f
+ @VisibleForTesting
+ fun propertyValue(name: String, scale: Float = 1f, default: Float = 0f): Float =
+ SystemProperties.getInt(
+ /* key= */ "$SYSTEM_PROPERTIES_GROUP.$name",
+ /* def= */ (default * scale).toInt()
+ ) / scale
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 84f6af41..72d1a76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -27,10 +27,13 @@
traces in Winscope)
### Kotlin
+Kotlin protologging is supported but not as optimized as in Java.
-Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
-class which has a similar API to the Java ProtoLog class.
+The Protolog tool does not yet have support for Kotlin code ([b/168581922](https://b.corp.google.com/issues/168581922)).
+
+What this implies is that ProtoLogs are not pre-processed to extract the static strings out when used in Kotlin. So,
+there is no memory gain when using ProtoLogging in Kotlin. The logs will still be traced to Perfetto, but with a subtly
+worse performance due to the additional string interning that needs to be done at run time instead of at build time.
### Enabling ProtoLog command line logging
Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 88f9e4c..d565776 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -134,9 +134,10 @@
Rect baseBounds, Rect targetBounds, float degrees) {
Matrix transformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
- final float scale = (float) targetBounds.width() / baseBounds.width();
+ final float scaleX = (float) targetBounds.width() / baseBounds.width();
+ final float scaleY = (float) targetBounds.height() / baseBounds.height();
- transformTensor.setScale(scale, scale);
+ transformTensor.setScale(scaleX, scaleY);
transformTensor.postTranslate(targetBounds.left, targetBounds.top);
transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 218d456..0324fdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -56,7 +56,6 @@
import kotlin.jvm.functions.Function0;
import java.util.Optional;
-import java.util.function.Consumer;
/**
* A helper to animate and manipulate the PiP.
@@ -134,18 +133,6 @@
private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
- @Nullable private Runnable mUpdateMovementBoundsRunnable;
-
- private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
- if (mPipBoundsState.getBounds().equals(newBounds)) {
- return;
- }
-
- mMenuController.updateMenuLayout(newBounds);
- mPipBoundsState.setBounds(newBounds);
- maybeUpdateMovementBounds();
- };
-
/**
* Whether we're springing to the touch event location (vs. moving it to that position
* instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
@@ -683,16 +670,6 @@
cleanUpHighPerfSessionMaybe();
}
- void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
- mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
- }
-
- private void maybeUpdateMovementBounds() {
- if (mUpdateMovementBoundsRunnable != null) {
- mUpdateMovementBoundsRunnable.run();
- }
- }
-
/**
* Notifies the floating coordinator that we're moving, and sets the animating to bounds so
* we return these bounds from
@@ -720,7 +697,7 @@
/**
* Directly resizes the PiP to the given {@param bounds}.
*/
- private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
// Do not carry out any resizing if we are dragging or physics animator is running.
return;
@@ -813,7 +790,7 @@
cleanUpHighPerfSessionMaybe();
// Signal that the transition is done - should update transition state by default.
- mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds, false /* configAtEnd */);
}
private void startResizeAnimation(SurfaceControl.Transaction startTx,
@@ -829,8 +806,6 @@
startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
destinationBounds, duration, 0f /* angle */);
animator.setAnimationEndCallback(() -> {
- mUpdateBoundsCallback.accept(destinationBounds);
-
// In case an ongoing drag/fling was present before a deterministic resize transition
// kicked in, we need to update the update bounds properly before cleaning in-motion
// state.
@@ -839,7 +814,7 @@
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
- mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds, true /* configAtEnd */);
});
animator.start();
}
@@ -849,7 +824,6 @@
// The physics animation ended, though we may not necessarily be done animating, such as
// when we're still dragging after moving out of the magnetic target. Only set the final
// bounds state and clear motion bounds completely if the whole animation is over.
- mPipBoundsState.setBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
}
mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index d28204a..f5ef64d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -50,7 +50,6 @@
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import java.io.PrintWriter;
-import java.util.function.Consumer;
/**
* Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -86,8 +85,6 @@
private final Rect mUserResizeBounds = new Rect();
private final Rect mDownBounds = new Rect();
private final Rect mStartBoundsAfterRelease = new Rect();
- private final Runnable mUpdateMovementBoundsRunnable;
- private final Consumer<Rect> mUpdateResizeBoundsCallback;
private float mTouchSlop;
@@ -121,7 +118,6 @@
PipTouchState pipTouchState,
PipScheduler pipScheduler,
PipTransitionState pipTransitionState,
- Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger,
PhonePipMenuController menuActivityController,
ShellExecutor mainExecutor,
@@ -138,18 +134,9 @@
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
- mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
-
- mUpdateResizeBoundsCallback = (rect) -> {
- mUserResizeBounds.set(rect);
- // mMotionHelper.synchronizePinnedStackBounds();
- mPipBoundsState.setBounds(rect);
- mUpdateMovementBoundsRunnable.run();
- resetState();
- };
}
void init() {
@@ -563,11 +550,13 @@
mLastResizeBounds, duration, mAngle);
animator.setAnimationEndCallback(() -> {
// All motion operations have actually finished, so make bounds cache updates.
- mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
+ mUserResizeBounds.set(mLastResizeBounds);
+ resetState();
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
- mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(
+ mLastResizeBounds, true /* configAtEnd */);
});
animator.start();
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index ac670cf..f4defdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -52,11 +52,14 @@
private final Context mContext;
private final PipBoundsState mPipBoundsState;
+ private final PhonePipMenuController mPipMenuController;
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
private PipSchedulerReceiver mSchedulerReceiver;
private PipTransitionController mPipTransitionController;
+ @Nullable private Runnable mUpdateMovementBoundsRunnable;
+
/**
* Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
* This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -94,10 +97,12 @@
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
+ PhonePipMenuController pipMenuController,
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState) {
mContext = context;
mPipBoundsState = pipBoundsState;
+ mPipMenuController = pipMenuController;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
@@ -189,9 +194,13 @@
* Signals to Core to finish the PiP resize transition.
* Note that we do not allow any actual WM Core changes at this point.
*
+ * @param toBounds destination bounds used only for internal state updates - not sent to Core.
* @param configAtEnd true if we are waiting for config updates at the end of the transition.
*/
- public void scheduleFinishResizePip(boolean configAtEnd) {
+ public void scheduleFinishResizePip(Rect toBounds, boolean configAtEnd) {
+ // Make updates to the internal state to reflect new bounds
+ onFinishingPipResize(toBounds);
+
SurfaceControl.Transaction tx = null;
if (configAtEnd) {
tx = new SurfaceControl.Transaction();
@@ -238,4 +247,23 @@
tx.setMatrix(leash, transformTensor, mMatrixTmp);
tx.apply();
}
+
+ void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+ mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+ }
+
+ private void maybeUpdateMovementBounds() {
+ if (mUpdateMovementBoundsRunnable != null) {
+ mUpdateMovementBoundsRunnable.run();
+ }
+ }
+
+ private void onFinishingPipResize(Rect newBounds) {
+ if (mPipBoundsState.getBounds().equals(newBounds)) {
+ return;
+ }
+ mPipBoundsState.setBounds(newBounds);
+ mPipMenuController.updateMenuLayout(newBounds);
+ maybeUpdateMovementBounds();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
new file mode 100644
index 0000000..7f16880
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.pip2.phone;
+
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
+
+import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+
+/**
+ * A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via
+ * Transitions framework directly.
+ * Hence, it's the intention to keep the usage of this class for a very limited set of cases.
+ */
+public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
+ PipTransitionState.PipTransitionStateChangedListener {
+ private static final int ASPECT_RATIO_CHANGE_DURATION = 250;
+ private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
+
+ private final Context mContext;
+ private final PipTransitionState mPipTransitionState;
+ private final PipScheduler mPipScheduler;
+ private final PipBoundsState mPipBoundsState;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final ShellExecutor mMainExecutor;
+ private final PictureInPictureParams mPictureInPictureParams =
+ new PictureInPictureParams.Builder().build();
+
+ private boolean mWaitingForAspectRatioChange = false;
+
+ public PipTaskListener(Context context,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
+ PipScheduler pipScheduler,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mPipTransitionState = pipTransitionState;
+ mPipScheduler = pipScheduler;
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mMainExecutor = mainExecutor;
+
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ mMainExecutor.execute(() -> {
+ shellTaskOrganizer.addListenerForType(this,
+ ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP);
+ });
+ }
+ }
+
+ void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
+ if (mPictureInPictureParams.equals(params)) {
+ return;
+ }
+ mPictureInPictureParams.copyOnlySet(params != null ? params
+ : new PictureInPictureParams.Builder().build());
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ PictureInPictureParams params = taskInfo.pictureInPictureParams;
+ if (mPictureInPictureParams.equals(params)) {
+ return;
+ }
+ setPictureInPictureParams(params);
+ float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
+ if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+ onAspectRatioChanged(newAspectRatio);
+ });
+ }
+ }
+
+ private void onAspectRatioChanged(float ratio) {
+ mPipBoundsState.setAspectRatio(ratio);
+
+ final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
+ mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio());
+ // Avoid scheduling a resize transition if destination bounds are unchanged, otherise
+ // we could end up with a no-op transition.
+ if (!destinationBounds.equals(mPipBoundsState.getBounds())) {
+ Bundle extra = new Bundle();
+ extra.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ }
+ }
+
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+ mWaitingForAspectRatioChange = extra.getBoolean(ANIMATING_ASPECT_RATIO_CHANGE);
+ if (!mWaitingForAspectRatioChange) break;
+
+ mPipScheduler.scheduleAnimateResizePip(
+ mPipBoundsAlgorithm.getAdjustedDestinationBounds(
+ mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio()),
+ false /* configAtEnd */, ASPECT_RATIO_CHANGE_DURATION);
+ break;
+ case PipTransitionState.CHANGING_PIP_BOUNDS:
+ final SurfaceControl.Transaction startTx = extra.getParcelable(
+ PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishTx = extra.getParcelable(
+ PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
+ final Rect destinationBounds = extra.getParcelable(
+ PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+ final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
+ Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash,
+ "Leash is null for bounds transition.");
+
+ if (mWaitingForAspectRatioChange) {
+ PipResizeAnimator animator = new PipResizeAnimator(mContext,
+ mPipTransitionState.mPinnedTaskLeash, startTx, finishTx,
+ destinationBounds,
+ mPipBoundsState.getBounds(), destinationBounds, duration,
+ 0f /* delta */);
+ animator.setAnimationEndCallback(() -> {
+ mPipScheduler.scheduleFinishResizePip(
+ destinationBounds, false /* configAtEnd */);
+ });
+ animator.start();
+ }
+ break;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index d75fa00..029f001 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -206,7 +206,7 @@
mMenuController.addListener(new PipMenuListener());
mGesture = new DefaultPipTouchGesture();
mMotionHelper = pipMotionHelper;
- mMotionHelper.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
+ mPipScheduler.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
@@ -219,8 +219,8 @@
menuController::hideMenu,
mainExecutor);
mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
- pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
- this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
+ pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
+ menuController, mainExecutor,
mPipPerfHintController);
mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index c6b1a72..44baabd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -87,6 +87,7 @@
//
private final Context mContext;
+ private final PipTaskListener mPipTaskListener;
private final PipScheduler mPipScheduler;
private final PipTransitionState mPipTransitionState;
@@ -118,6 +119,7 @@
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipTaskListener pipTaskListener,
PipScheduler pipScheduler,
PipTransitionState pipTransitionState,
PipUiStateChangeController pipUiStateChangeController) {
@@ -125,6 +127,7 @@
pipBoundsAlgorithm);
mContext = context;
+ mPipTaskListener = pipTaskListener;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
mPipTransitionState = pipTransitionState;
@@ -510,6 +513,7 @@
// cache the original task token to check for multi-activity case later
final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+ mPipTaskListener.setPictureInPictureParams(pipParams);
mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
pipParams, mPipBoundsAlgorithm);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90e3f7f..1e4b8b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -881,7 +881,7 @@
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
mController.mBackAnimationAdapter.getRunner().onAnimationStart(
- targets, null, null, mBackAnimationFinishedCallback);
+ targets, null /* prepareOpenTransition */, mBackAnimationFinishedCallback);
mShellExecutor.flushAll();
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index b39cf19..d5287e7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -35,9 +35,12 @@
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -90,6 +93,9 @@
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private CompatUIController mController;
private ShellInit mShellInit;
@Mock
@@ -122,7 +128,6 @@
private CompatUIConfiguration mCompatUIConfiguration;
@Mock
private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
-
@Mock
private AccessibilityManager mAccessibilityManager;
@@ -132,6 +137,8 @@
@NonNull
private CompatUIStatusManager mCompatUIStatusManager;
+ private boolean mInDesktopModePredicateResult;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -157,7 +164,7 @@
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
- mCompatUIStatusManager) {
+ mCompatUIStatusManager, i -> mInDesktopModePredicateResult) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -685,6 +692,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false);
@@ -695,6 +703,34 @@
eq(mMockTaskListener));
}
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+ public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
+ mInDesktopModePredicateResult = false;
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+ mInDesktopModePredicateResult = true;
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController).removeLayouts(taskInfo.taskId);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+ public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
+ mInDesktopModePredicateResult = false;
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+ mInDesktopModePredicateResult = true;
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+ }
+
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
/* isFocused */ false, /* isTopActivityTransparent */ false);
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 16a234b..5b02837 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
@@ -8,6 +8,7 @@
import android.app.WindowConfiguration.WindowingMode
import android.graphics.PointF
import android.os.IBinder
+import android.os.SystemProperties
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
@@ -16,6 +17,7 @@
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
@@ -29,19 +31,24 @@
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import java.util.function.Supplier
+import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.MockitoSession
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
/** Tests of [DragToDesktopTransitionHandler]. */
@SmallTest
@@ -61,10 +68,12 @@
private lateinit var defaultHandler: DragToDesktopTransitionHandler
private lateinit var springHandler: SpringDragToDesktopTransitionHandler
+ private lateinit var mockitoSession: MockitoSession
@Before
fun setUp() {
- defaultHandler = DefaultDragToDesktopTransitionHandler(
+ defaultHandler =
+ DefaultDragToDesktopTransitionHandler(
context,
transitions,
taskDisplayAreaOrganizer,
@@ -72,7 +81,8 @@
transactionSupplier,
)
.apply { setSplitScreenController(splitScreenController) }
- springHandler = SpringDragToDesktopTransitionHandler(
+ springHandler =
+ SpringDragToDesktopTransitionHandler(
context,
transitions,
taskDisplayAreaOrganizer,
@@ -80,6 +90,16 @@
transactionSupplier,
)
.apply { setSplitScreenController(splitScreenController) }
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties::class.java)
+ .startMocking()
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
}
@Test
@@ -357,6 +377,77 @@
verify(finishCallback).onTransitionFinished(null)
}
+ @Test
+ fun propertyValue_returnsSystemPropertyValue() {
+ val name = "property_name"
+ val value = 10f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+ .thenReturn(value.toInt())
+
+ assertEquals(
+ "Expects to return system properties stored value",
+ /* expected= */ value,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name)
+ )
+ }
+
+ @Test
+ fun propertyValue_withScale_returnsScaledSystemPropertyValue() {
+ val name = "property_name"
+ val value = 10f
+ val scale = 100f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+ .thenReturn(value.toInt())
+
+ assertEquals(
+ "Expects to return scaled system properties stored value",
+ /* expected= */ value / scale,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale)
+ )
+ }
+
+ @Test
+ fun propertyValue_notSet_returnsDefaultValue() {
+ val name = "property_name"
+ val defaultValue = 50f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(defaultValue.toInt())))
+ .thenReturn(defaultValue.toInt())
+
+ assertEquals(
+ "Expects to return the default value",
+ /* expected= */ defaultValue,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+ name,
+ default = defaultValue
+ )
+ )
+ }
+
+ @Test
+ fun propertyValue_withScaleNotSet_returnsDefaultValue() {
+ val name = "property_name"
+ val defaultValue = 0.5f
+ val scale = 100f
+ // Default value is multiplied when provided as a default value for [SystemProperties]
+ val scaledDefault = (defaultValue * scale).toInt()
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(scaledDefault)))
+ .thenReturn(scaledDefault)
+
+ assertEquals(
+ "Expects to return the default value",
+ /* expected= */ defaultValue,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+ name,
+ default = defaultValue,
+ scale = scale
+ )
+ )
+ }
+
private fun startDrag(
handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo = createTask(),
@@ -462,4 +553,7 @@
)
}
}
+
+ private fun systemPropertiesKey(name: String) =
+ "${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
}
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index fd596d9..e427c97 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -16,6 +16,7 @@
#include "tests/common/TestContext.h"
+#include <com_android_graphics_libgui_flags.h>
#include <cutils/trace.h>
namespace android {
@@ -101,6 +102,14 @@
}
void TestContext::createOffscreenSurface() {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ mConsumer = new BufferItemConsumer(GRALLOC_USAGE_HW_COMPOSER, 4);
+ const ui::Size& resolution = getActiveDisplayResolution();
+ mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
+ mSurface = mConsumer->getSurface();
+ mSurface->setMaxDequeuedBufferCount(3);
+ mSurface->setAsyncMode(true);
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -110,6 +119,7 @@
const ui::Size& resolution = getActiveDisplayResolution();
mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
mSurface = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
}
void TestContext::waitForVsync() {
@@ -144,4 +154,4 @@
} // namespace test
} // namespace uirenderer
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ca468fc..029e6f4 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -10128,6 +10128,24 @@
/**
* @hide
+ * Blocks until permission updates have propagated through the audio system.
+ * Only useful in tests, where adoptShellPermissions can change the permission state of
+ * an app without the app being killed.
+ */
+ @TestApi
+ @SuppressWarnings("UnflaggedApi") // @TestApi without associated feature.
+ public void permissionUpdateBarrier() {
+ final IAudioService service = getService();
+ try {
+ service.permissionUpdateBarrier();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * @hide
* Return the list of independent stream types for volume control.
* A stream type is considered independent when the volume changes of that type do not
* affect any other independent volume control stream type.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d20b7f0..e0c3461 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -101,6 +101,8 @@
oneway void portEvent(in int portId, in int event, in @nullable PersistableBundle extras);
+ void permissionUpdateBarrier();
+
// Java-only methods below.
void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 371e3d2..019b1e0 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -17,35 +17,31 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "ImageReader_JNI"
#define ATRACE_TAG ATRACE_TAG_CAMERA
-#include "android_media_Utils.h"
+#include <android/hardware_buffer_jni.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android_runtime/android_view_Surface.h>
+#include <com_android_graphics_libgui_flags.h>
#include <cutils/atomic.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
+#include <grallocusage/GrallocUsageConversion.h>
+#include <gui/BufferItemConsumer.h>
+#include <gui/Surface.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <stdint.h>
+#include <ui/Rect.h>
#include <utils/List.h>
-#include <utils/Trace.h>
+#include <utils/Log.h>
#include <utils/String8.h>
+#include <utils/Trace.h>
+#include <utils/misc.h>
#include <cstdio>
-#include <gui/BufferItemConsumer.h>
-#include <gui/Surface.h>
-
-#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/android_view_Surface.h>
-#include <android_runtime/android_graphics_GraphicBuffer.h>
-#include <android_runtime/android_hardware_HardwareBuffer.h>
-#include <grallocusage/GrallocUsageConversion.h>
-
-#include <private/android/AHardwareBufferHelpers.h>
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-#include <android/hardware_buffer_jni.h>
-
-#include <ui/Rect.h>
+#include "android_media_Utils.h"
#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext"
#define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID "mNativeBuffer"
@@ -393,18 +389,25 @@
}
sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages));
- sp<IGraphicBufferProducer> gbProducer;
- sp<IGraphicBufferConsumer> gbConsumer;
- BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
- sp<BufferItemConsumer> bufferConsumer;
String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
width, height, nativeHalFormat, maxImages, getpid(),
createProcessUniqueId());
uint64_t consumerUsage =
android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<BufferItemConsumer> bufferConsumer = new BufferItemConsumer(consumerUsage, maxImages,
+ /*controlledByApp*/ true);
+ sp<IGraphicBufferProducer> gbProducer =
+ bufferConsumer->getSurface()->getIGraphicBufferProducer();
+#else
+ sp<IGraphicBufferProducer> gbProducer;
+ sp<IGraphicBufferConsumer> gbConsumer;
+ BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
+ sp<BufferItemConsumer> bufferConsumer;
bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
/*controlledByApp*/true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
if (bufferConsumer == nullptr) {
jniThrowExceptionFmt(env, "java/lang/RuntimeException",
"Failed to allocate native buffer consumer for hal format 0x%x and usage 0x%x",
@@ -413,7 +416,11 @@
}
if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ bufferConsumer->setConsumerIsProtected(true);
+#else
gbConsumer->setConsumerIsProtected(true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
}
ctx->setBufferConsumer(bufferConsumer);
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index 1bb82f8..4637ccd 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -15,21 +15,23 @@
*/
// #define LOG_NDEBUG 0
-#include "base/logging.h"
-#include "base/utilities.h"
#include "core/gl_env.h"
-#include "core/shader_program.h"
-#include "core/vertex_frame.h"
-#include "system/window.h"
+
+#include <EGL/eglext.h>
+#include <com_android_graphics_libgui_flags.h>
+#include <gui/BufferQueue.h>
+#include <gui/GLConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
#include <map>
#include <string>
-#include <EGL/eglext.h>
-#include <gui/BufferQueue.h>
-#include <gui/Surface.h>
-#include <gui/GLConsumer.h>
-#include <gui/IGraphicBufferProducer.h>
+#include "base/logging.h"
+#include "base/utilities.h"
+#include "core/shader_program.h"
+#include "core/vertex_frame.h"
+#include "system/window.h"
namespace android {
namespace filterfw {
@@ -165,12 +167,18 @@
}
// Create dummy surface using a GLConsumer
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ surfaceTexture_ = new GLConsumer(0, GLConsumer::TEXTURE_EXTERNAL, /*useFenceSync=*/true,
+ /*isControlledByApp=*/false);
+ window_ = surfaceTexture_->getSurface();
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
surfaceTexture_ = new GLConsumer(consumer, 0, GLConsumer::TEXTURE_EXTERNAL,
true, false);
window_ = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
if (CheckEGLError("eglCreateWindowSurface")) return false;
diff --git a/media/tests/mediatestutils/Android.bp b/media/tests/mediatestutils/Android.bp
index 88938e2..8c68f21 100644
--- a/media/tests/mediatestutils/Android.bp
+++ b/media/tests/mediatestutils/Android.bp
@@ -27,9 +27,11 @@
name: "mediatestutils",
srcs: [
"java/com/android/media/mediatestutils/TestUtils.java",
+ "java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java",
],
static_libs: [
"androidx.concurrent_concurrent-futures",
+ "androidx.test.runner",
"guava",
"mediatestutils_host",
],
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java
new file mode 100644
index 0000000..c51b5de
--- /dev/null
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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.media.mediatestutils;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Barrier to wait for permission updates to propagate to audioserver, to avoid flakiness when using
+ * {@code com.android.compatability.common.util.AdoptShellPermissionsRule}. Note, this rule should
+ * <b> always </b> be placed after the adopt permission rule. Don't use rule when changing
+ * permission state in {@code @Before}, since that executes after all rules.
+ */
+public class PermissionUpdateBarrierRule implements TestRule {
+
+ private final Context mContext;
+
+ /**
+ * @param context the context to use
+ */
+ public PermissionUpdateBarrierRule(Context context) {
+ mContext = context;
+ }
+
+ public PermissionUpdateBarrierRule() {
+ this(InstrumentationRegistry.getInstrumentation().getContext());
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ mContext.getSystemService(AudioManager.class).permissionUpdateBarrier();
+ base.evaluate();
+ }
+ };
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index d36b55f..9fa8fc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -286,8 +286,7 @@
/**
* Returns the mode icon -- which can be either app-provided (via {@code addAutomaticZenRule}),
- * user-chosen (via the icon picker in Settings), the app's launcher icon for implicit rules
- * (in its monochrome variant, if available), or a default icon based on the mode type.
+ * user-chosen (via the icon picker in Settings), or a default icon based on the mode type.
*/
@NonNull
public ListenableFuture<Drawable> getIcon(@NonNull Context context,
@@ -300,23 +299,6 @@
return iconLoader.getIcon(context, mRule);
}
- /**
- * Returns an alternative mode icon. The difference with {@link #getIcon} is that it's the
- * basic DND icon not only for Manual DND, but also for <em>implicit rules</em>. As such, it's
- * suitable for places where showing the launcher icon of an app could be confusing, such as
- * the status bar or lockscreen.
- */
- @NonNull
- public ListenableFuture<Drawable> getLockscreenIcon(@NonNull Context context,
- @NonNull ZenIconLoader iconLoader) {
- if (mKind == Kind.MANUAL_DND || mKind == Kind.IMPLICIT) {
- return Futures.immediateFuture(requireNonNull(
- context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
- }
-
- return iconLoader.getIcon(context, mRule);
- }
-
@NonNull
public ZenPolicy getPolicy() {
switch (mRule.getInterruptionFilter()) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS
new file mode 100644
index 0000000..134a56e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS
@@ -0,0 +1 @@
+include /packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index f533e77..32216fa 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -322,41 +322,6 @@
verify(iconLoader).getIcon(any(), eq(IMPLICIT_ZEN_RULE));
}
- @Test
- public void getLockscreenIcon_normalMode_loadsIconNormally() {
- ZenIconLoader iconLoader = mock(ZenIconLoader.class);
- ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
-
- ListenableFuture<Drawable> unused = mode.getLockscreenIcon(
- RuntimeEnvironment.getApplication(), iconLoader);
-
- verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
- }
-
- @Test
- public void getLockscreenIcon_manualDnd_returnsFixedIcon() {
- ZenIconLoader iconLoader = mock(ZenIconLoader.class);
-
- ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getLockscreenIcon(
- RuntimeEnvironment.getApplication(), iconLoader);
-
- assertThat(future.isDone()).isTrue();
- verify(iconLoader, never()).getIcon(any(), any());
- }
-
- @Test
- public void getLockscreenIcon_implicitMode_returnsFixedIcon() {
- ZenIconLoader iconLoader = mock(ZenIconLoader.class);
- ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
- zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
-
- ListenableFuture<Drawable> future = mode.getLockscreenIcon(
- RuntimeEnvironment.getApplication(), iconLoader);
-
- assertThat(future.isDone()).isTrue();
- verify(iconLoader, never()).getIcon(any(), any());
- }
-
private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
Parcel parcel = Parcel.obtain();
try {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a129ac1..8a1d81b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1120,16 +1120,6 @@
}
flag {
- name: "glanceable_hub_back_gesture"
- namespace: "systemui"
- description: "Enables back gesture on the glanceable hub"
- bug: "346331399"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "glanceable_hub_allow_keyguard_when_dreaming"
namespace: "systemui"
description: "Allows users to exit dream to keyguard with glanceable hub enabled"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index aeba67b..5a4020d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -31,6 +31,7 @@
import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -56,7 +57,7 @@
private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
private val dialogFactory: BouncerDialogFactory,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Bouncer
private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
@@ -66,7 +67,7 @@
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 872bef2..ed12776 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -31,7 +31,6 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -47,7 +46,6 @@
import com.android.compose.animation.scene.transitions
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.internal.R.attr.focusable
-import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -198,15 +196,7 @@
Box(modifier = Modifier.fillMaxSize())
}
- val userActions =
- if (glanceableHubBackGesture()) {
- mapOf(
- Swipe(SwipeDirection.End) to CommunalScenes.Blank,
- Back to CommunalScenes.Blank,
- )
- } else {
- mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
- }
+ val userActions = mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
scene(CommunalScenes.Communal, userActions = userActions) {
CommunalScene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 6f415ea..b0590e0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -545,18 +545,35 @@
) {
val coroutineScope = rememberCoroutineScope()
val liveContentKeys = remember { mutableListOf<String>() }
+ var communalContentPending by remember { mutableStateOf(true) }
LaunchedEffect(communalContent) {
+ // Do nothing until any communal content comes in
+ if (communalContentPending && communalContent.isEmpty()) {
+ return@LaunchedEffect
+ }
+
val prevLiveContentKeys = liveContentKeys.toList()
+ val newLiveContentKeys = communalContent.filter { it.isLiveContent() }.map { it.key }
liveContentKeys.clear()
- liveContentKeys.addAll(communalContent.filter { it.isLiveContent() }.map { it.key })
+ liveContentKeys.addAll(newLiveContentKeys)
- // Find the first updated content
+ // Do nothing on first communal content since we don't have a delta
+ if (communalContentPending) {
+ communalContentPending = false
+ return@LaunchedEffect
+ }
+
+ // Do nothing if there is no new live content
val indexOfFirstUpdatedContent =
- liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
+ newLiveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
+ if (indexOfFirstUpdatedContent < 0) {
+ return@LaunchedEffect
+ }
- // Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
+ // Scroll if the live content is not visible
+ val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
+ if (lastVisibleItemIndex != null && indexOfFirstUpdatedContent > lastVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 6750e41..54ffcf4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -27,10 +27,12 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -44,7 +46,7 @@
private val dialogFactory: SystemUIDialogFactory,
private val interactionHandler: WidgetInteractionHandler,
private val widgetSection: CommunalAppWidgetSection,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Communal
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
@@ -55,6 +57,10 @@
)
.asStateFlow()
+ override suspend fun onActivated(): Nothing {
+ awaitCancellation()
+ }
+
@Composable
override fun SceneScope.Content(modifier: Modifier) {
CommunalHub(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7f059d7..2029e9e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -25,6 +25,7 @@
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -39,7 +40,7 @@
constructor(
actionsViewModelFactory: LockscreenSceneActionsViewModel.Factory,
private val lockscreenContent: Lazy<LockscreenContent>,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Lockscreen
private val actionsViewModel: LockscreenSceneActionsViewModel by lazy {
@@ -49,7 +50,7 @@
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 666e324..62213bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -28,6 +28,7 @@
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -61,7 +62,7 @@
private val shadeSession: SaveableSession,
private val stackScrollView: Lazy<NotificationScrollView>,
private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.NotificationsShade
@@ -72,7 +73,7 @@
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d16ba0d..f11f8bb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -81,6 +81,7 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -129,7 +130,7 @@
private val statusBarIconController: StatusBarIconController,
private val mediaCarouselController: MediaCarouselController,
@Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.QuickSettings
private val actionsViewModel: QuickSettingsSceneActionsViewModel by lazy {
@@ -139,7 +140,7 @@
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index fb7c422..b25773b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -43,6 +43,7 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -74,7 +75,7 @@
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.QuickSettingsShade
@@ -85,6 +86,10 @@
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
+ override suspend fun onActivated(): Nothing {
+ actionsViewModel.activate()
+ }
+
@Composable
override fun SceneScope.Content(
modifier: Modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 3e22105..d489d73 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -26,6 +26,7 @@
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.ui.composable.QuickSettings
@@ -50,7 +51,7 @@
private val notificationStackScrolLView: Lazy<NotificationScrollView>,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
private val viewModelFactory: GoneSceneActionsViewModel.Factory,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Gone
private val actionsViewModel: GoneSceneActionsViewModel by lazy { viewModelFactory.create() }
@@ -58,7 +59,7 @@
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 853dc6f..f8513a8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -82,6 +82,7 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.composable.MediaContentPicker
@@ -160,7 +161,7 @@
private val mediaCarouselController: MediaCarouselController,
@Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
@Named(QS_PANEL) private val qsMediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Shade
@@ -168,7 +169,7 @@
actionsViewModelFactory.create()
}
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 5cc194d..2b5953c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -523,8 +523,8 @@
fun convert(progress: Float): Float
companion object {
- /** Keeps scrolling linearly */
- val Default = linear()
+ /** Starts linearly with some resistance and slowly approaches to 0.2f */
+ val Default = tanh(maxProgress = 0.2f, tilt = 3f)
/**
* The scroll stays linear, with [factor] you can control how much resistance there is.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 682fe95..770c0f8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -724,6 +724,7 @@
layoutHeight = layoutHeight,
sceneTransitions = {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
// On overscroll 100% -> Foo should translate by overscrollTranslateY
translate(TestElements.Foo, y = overscrollTranslateY)
}
@@ -780,6 +781,7 @@
transitions =
transitions {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
translate(TestElements.Foo, y = overscrollTranslateY)
}
}
@@ -921,6 +923,7 @@
layoutHeight = layoutHeight,
sceneTransitions = {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
// On overscroll 100% -> Foo should translate by layoutHeight
translate(TestElements.Foo, y = { absoluteDistance })
}
@@ -1015,7 +1018,7 @@
layoutHeight = layoutHeight,
sceneTransitions = {
// Overscroll progress will be linear (by default)
- defaultOverscrollProgressConverter = ProgressConverter { it }
+ defaultOverscrollProgressConverter = ProgressConverter.linear()
overscroll(SceneB, Orientation.Vertical) {
// This override the defaultOverscrollProgressConverter
@@ -1125,6 +1128,7 @@
)
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
// On overscroll 100% -> Foo should translate by layoutHeight
translate(TestElements.Foo, y = { absoluteDistance })
}
@@ -1861,6 +1865,7 @@
SceneA,
transitions {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
translate(TestElements.Foo, y = 15.dp)
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 06799bc..e48cd817 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -614,6 +614,7 @@
from(SceneA, to = SceneB) { distance = FixedDistance(swipeDistance) }
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
translate(TestElements.Foo, x = { 20.dp.toPx() }, y = { 30.dp.toPx() })
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS
new file mode 100644
index 0000000..f6f98e9
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/SystemActionsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/SystemActionsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/InputSessionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/InputSessionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackTransformationTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackTransformationTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatterySpecsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatterySpecsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraIntentsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraIntentsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/HistoryTrackerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/HistoryTrackerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ProximityClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ProximityClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TypeClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TypeClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
index 5556b04..4c908dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
@@ -140,6 +140,41 @@
}
@Test
+ fun installSessions_ignoreNullPackageNameSessions() =
+ testScope.runTest {
+ val nullPackageSession =
+ SessionInfo().apply {
+ sessionId = 1
+ appPackageName = null
+ appIcon = icon1
+ }
+ val wellFormedSession =
+ SessionInfo().apply {
+ sessionId = 2
+ appPackageName = "pkg_name"
+ appIcon = icon2
+ }
+
+ defaultSessions = listOf(nullPackageSession, wellFormedSession)
+
+ whenever(packageInstaller.allSessions).thenReturn(defaultSessions)
+ whenever(packageInstaller.getSessionInfo(1)).thenReturn(nullPackageSession)
+ whenever(packageInstaller.getSessionInfo(2)).thenReturn(wellFormedSession)
+
+ val packageInstallerMonitor =
+ PackageInstallerMonitor(
+ handler,
+ kosmos.applicationCoroutineScope,
+ logcatLogBuffer("PackageInstallerRepositoryImplTest"),
+ packageInstaller,
+ )
+
+ val sessions by
+ testScope.collectLastValue(packageInstallerMonitor.installSessionsForPrimaryUser)
+ assertThat(sessions?.size).isEqualTo(1)
+ }
+
+ @Test
fun installSessions_newSessionsAreAdded() =
testScope.runTest {
val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
@@ -177,7 +212,7 @@
}
// Session 1 finished successfully
- callback.onFinished(1, /* success = */ true)
+ callback.onFinished(1, /* success= */ true)
runCurrent()
// Verify flow updated with session 1 removed
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 864795b..1d03ced 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -791,14 +791,6 @@
}
@Test
- fun showWidgetEditor_withPreselectedKey_startsActivity() =
- testScope.runTest {
- val widgetKey = CommunalContentModel.KEY.widget(123)
- underTest.showWidgetEditor(preselectedKey = widgetKey)
- verify(editWidgetsActivityStarter).startActivity(widgetKey)
- }
-
- @Test
fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() =
testScope.runTest {
underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true)
@@ -1082,6 +1074,16 @@
assertThat(disclaimerDismissed).isFalse()
}
+ @Test
+ fun settingSelectedKey_flowUpdated() {
+ testScope.runTest {
+ val key = "test"
+ val selectedKey by collectLastValue(underTest.selectedKey)
+ underTest.setSelectedKey(key)
+ assertThat(selectedKey).isEqualTo(key)
+ }
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index ed7e910..dfb75ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor.OnSceneAboutToChangeListener
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.EditModeState
@@ -36,6 +37,11 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -58,6 +64,36 @@
}
@Test
+ fun changeScene_callsSceneStateProcessor() =
+ testScope.runTest {
+ val callback: OnSceneAboutToChangeListener = mock()
+ underTest.registerSceneStateProcessor(callback)
+
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ verify(callback, never()).onSceneAboutToChange(any(), anyOrNull())
+
+ underTest.changeScene(CommunalScenes.Communal, "test")
+ assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+ verify(callback).onSceneAboutToChange(CommunalScenes.Communal, null)
+ }
+
+ @Test
+ fun changeScene_doesNotCallSceneStateProcessorForDuplicateState() =
+ testScope.runTest {
+ val callback: OnSceneAboutToChangeListener = mock()
+ underTest.registerSceneStateProcessor(callback)
+
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ underTest.changeScene(CommunalScenes.Blank, "test")
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ verify(callback, never()).onSceneAboutToChange(any(), anyOrNull())
+ }
+
+ @Test
fun snapToScene() =
testScope.runTest {
val currentScene by collectLastValue(underTest.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 7e28e19..0bfcd24 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -36,11 +36,15 @@
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTutorialInteractorTest : SysuiTestCase() {
@@ -50,14 +54,14 @@
private lateinit var underTest: CommunalTutorialInteractor
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
- private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
private lateinit var userRepository: FakeUserRepository
@Before
fun setUp() {
keyguardRepository = kosmos.fakeKeyguardRepository
communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
- communalInteractor = kosmos.communalInteractor
+ communalSceneInteractor = kosmos.communalSceneInteractor
userRepository = kosmos.fakeUserRepository
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -158,7 +162,7 @@
kosmos.setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
- communalInteractor.changeScene(CommunalScenes.Blank, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
}
@@ -171,7 +175,7 @@
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
- communalInteractor.changeScene(CommunalScenes.Blank, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
@@ -184,13 +188,14 @@
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalInteractor.changeScene(CommunalScenes.Blank, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
- private suspend fun goToCommunal() {
+ private suspend fun TestScope.goToCommunal() {
kosmos.setCommunalAvailable(true)
- communalInteractor.changeScene(CommunalScenes.Communal, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index fb151a8..f6f5bc0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -159,24 +159,27 @@
communalInteractor = spy(kosmos.communalInteractor)
- underTest =
- CommunalViewModel(
- kosmos.testDispatcher,
- testScope,
- kosmos.testScope.backgroundScope,
- context.resources,
- kosmos.keyguardTransitionInteractor,
- kosmos.keyguardInteractor,
- mock<KeyguardIndicationController>(),
- kosmos.communalSceneInteractor,
- communalInteractor,
- kosmos.communalSettingsInteractor,
- kosmos.communalTutorialInteractor,
- kosmos.shadeInteractor,
- mediaHost,
- logcatLogBuffer("CommunalViewModelTest"),
- metricsLogger,
- )
+ underTest = createViewModel()
+ }
+
+ private fun createViewModel(): CommunalViewModel {
+ return CommunalViewModel(
+ kosmos.testDispatcher,
+ testScope,
+ kosmos.testScope.backgroundScope,
+ context.resources,
+ kosmos.keyguardTransitionInteractor,
+ kosmos.keyguardInteractor,
+ mock<KeyguardIndicationController>(),
+ kosmos.communalSceneInteractor,
+ communalInteractor,
+ kosmos.communalSettingsInteractor,
+ kosmos.communalTutorialInteractor,
+ kosmos.shadeInteractor,
+ mediaHost,
+ logcatLogBuffer("CommunalViewModelTest"),
+ metricsLogger,
+ )
}
@Test
@@ -784,6 +787,21 @@
assertThat(touchAvailable).isTrue()
}
+ @Test
+ fun selectedKey_changeAffectsAllInstances() =
+ testScope.runTest {
+ val model1 = createViewModel()
+ val selectedKey1 by collectLastValue(model1.selectedKey)
+ val model2 = createViewModel()
+ val selectedKey2 by collectLastValue(model2.selectedKey)
+
+ val key = "test"
+ model1.setSelectedKey(key)
+
+ assertThat(selectedKey1).isEqualTo(key)
+ assertThat(selectedKey2).isEqualTo(key)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
index 5b629b9..48b42d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
@@ -21,7 +21,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.testKosmos
@@ -62,7 +61,7 @@
fun activityLaunch_intentIsWellFormed() {
with(kosmos) {
testScope.runTest {
- underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = true)
+ underTest.startActivity(shouldOpenWidgetPickerOnStart = true)
val captor = argumentCaptor<Intent>()
verify(activityStarter)
@@ -71,8 +70,6 @@
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
.isNotEqualTo(0)
- assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
- .isEqualTo(TEST_PRESELECTED_KEY)
assertThat(
captor.lastValue.extras?.getBoolean(
EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
@@ -80,7 +77,7 @@
)
.isEqualTo(true)
- underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = false)
+ underTest.startActivity(shouldOpenWidgetPickerOnStart = false)
verify(activityStarter, times(2))
.startActivityDismissingKeyguard(captor.capture(), eq(true), eq(true), any())
@@ -88,8 +85,6 @@
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
.isNotEqualTo(0)
- assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
- .isEqualTo(TEST_PRESELECTED_KEY)
assertThat(
captor.lastValue.extras?.getBoolean(
EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
@@ -99,8 +94,4 @@
}
}
}
-
- companion object {
- const val TEST_PRESELECTED_KEY = "test-key"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/CustomIconCacheTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/CustomIconCacheTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AllModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AllModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AppAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AppAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/StartActivityData.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/StartActivityData.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/coroutines/FlowTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/coroutines/FlowTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 88ba041..296a0fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -88,21 +88,14 @@
}
@Test
- fun testWakeUpSetsExitAnimationsRunning() {
- controller.wakeUp()
-
- verify(stateController).setExitAnimationsRunning(true)
- }
-
- @Test
- fun testWakeUpAfterStartWillCancel() {
+ fun testOnWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
- controller.wakeUp()
+ controller.onWakeUp()
// Verify that we cancelled the start animator in favor of the exit
// animator.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 29aa89c..eda9039 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -664,14 +664,14 @@
)
mMainExecutor.runAllReady()
mService.onWakeUp()
- verify(mDreamOverlayContainerViewController).wakeUp()
+ verify(mDreamOverlayContainerViewController).onWakeUp()
verify(mDreamOverlayCallbackController).onWakeUp()
}
@Test
fun testWakeUpBeforeStartDoesNothing() {
mService.onWakeUp()
- verify(mDreamOverlayContainerViewController, Mockito.never()).wakeUp()
+ verify(mDreamOverlayContainerViewController, Mockito.never()).onWakeUp()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
deleted file mode 100644
index f6f58c9..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2024 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.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.lifecycle
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BaseActivatableTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private val underTest = FakeActivatable()
-
- @Test
- fun activate() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- underTest.activateIn(testScope)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
- }
-
- @Test
- fun activate_andCancel() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- val job = Job()
- underTest.activateIn(testScope, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(1)
- }
-
- @Test
- fun activate_afterCancellation() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- val job = Job()
- underTest.activateIn(testScope, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(1)
-
- underTest.activateIn(testScope)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(2)
- assertThat(underTest.cancellationCount).isEqualTo(1)
- }
-
- @Test(expected = IllegalStateException::class)
- fun activate_whileActive_throws() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- underTest.activateIn(testScope)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- underTest.activateIn(testScope)
- runCurrent()
- }
-
- @Test
- fun addChild_beforeActive_activatesChildrenOnceActivated() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.activateIn(this)
- runCurrent()
-
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun addChild_whileActive_activatesChildrenImmediately() =
- testScope.runTest {
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun addChild_afterCancellation_doesNotActivateChildren() =
- testScope.runTest {
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
- }
-
- @Test
- fun activate_cancellation_cancelsCurrentChildren() =
- testScope.runTest {
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
- }
-
- @Test
- fun activate_afterCancellation_reactivatesCurrentChildren() =
- testScope.runTest {
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun removeChild_beforeActive_neverActivatesChild() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
- }
-
- @Test
- fun removeChild_whileActive_cancelsChild() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- underTest.removeChild(child1)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun removeChild_afterCancellation_doesNotReactivateChildren() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.removeChild(child1)
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isTrue()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt
new file mode 100644
index 0000000..81b9180
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.lifecycle
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ExclusiveActivatableTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest = FakeActivatable()
+
+ @Test
+ fun activate() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+ }
+
+ @Test
+ fun activate_andCancel() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ val job = Job()
+ underTest.activateIn(testScope, context = job)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ job.cancel()
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+ }
+
+ @Test
+ fun activate_afterCancellation() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ val job = Job()
+ underTest.activateIn(testScope, context = job)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ job.cancel()
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(2)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun activate_whileActive_throws() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
new file mode 100644
index 0000000..8c9c527
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.systemui.lifecycle
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HydratorTest : SysuiTestCase() {
+
+ @get:Rule val composeRule = createComposeRule()
+
+ @Test
+ fun hydratedStateOf() {
+ val keepAliveMutable = mutableStateOf(true)
+ val upstreamStateFlow = MutableStateFlow(true)
+ val upstreamFlow = upstreamStateFlow.map { !it }
+ composeRule.setContent {
+ val keepAlive by keepAliveMutable
+ if (keepAlive) {
+ val viewModel = rememberViewModel {
+ FakeSysUiViewModel(
+ upstreamFlow = upstreamFlow,
+ upstreamStateFlow = upstreamStateFlow,
+ )
+ }
+
+ Column {
+ Text(
+ "upstreamStateFlow=${viewModel.stateBackedByStateFlow}",
+ Modifier.testTag("upstreamStateFlow")
+ )
+ Text(
+ "upstreamFlow=${viewModel.stateBackedByFlow}",
+ Modifier.testTag("upstreamFlow")
+ )
+ }
+ }
+ }
+
+ composeRule.waitForIdle()
+ composeRule
+ .onNode(hasTestTag("upstreamStateFlow"))
+ .assertTextEquals("upstreamStateFlow=true")
+ composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false")
+
+ composeRule.runOnUiThread { upstreamStateFlow.value = false }
+ composeRule.waitForIdle()
+ composeRule
+ .onNode(hasTestTag("upstreamStateFlow"))
+ .assertTextEquals("upstreamStateFlow=false")
+ composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
index 1118a61..e2149d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -26,7 +26,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.shared.model.MediaData
@@ -82,7 +81,6 @@
footerActionsController = footerActionsController,
mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
)
- underTest.activateIn(testScope)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
index 206d3ac..dd4432d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
@@ -55,7 +55,6 @@
testScope.runTest {
val actions by collectLastValue(underTest.actions)
- assertThat(underTest.isActive).isFalse()
assertThat(actions).isEmpty()
}
@@ -66,7 +65,6 @@
underTest.activateIn(testScope)
runCurrent()
- assertThat(underTest.isActive).isTrue()
assertThat(actions).isEmpty()
}
@@ -76,7 +74,6 @@
val actions by collectLastValue(underTest.actions)
underTest.activateIn(testScope)
runCurrent()
- assertThat(underTest.isActive).isTrue()
val expected1 =
mapOf(
@@ -116,7 +113,6 @@
job.cancel()
runCurrent()
- assertThat(underTest.isActive).isFalse()
assertThat(actions).isEmpty()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
new file mode 100644
index 0000000..a310ef4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
+import com.android.systemui.statusbar.notification.row.shared.IconModel
+import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
+class EnRouteViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val repository = kosmos.fakeNotificationRowRepository
+
+ private var contentModel: EnRouteContentModel?
+ get() = repository.richOngoingContentModel.value as? EnRouteContentModel
+ set(value) {
+ repository.richOngoingContentModel.value = value
+ }
+
+ private lateinit var underTest: EnRouteViewModel
+
+ @Before
+ fun setup() {
+ underTest = kosmos.getEnRouteViewModel(repository)
+ }
+
+ @Test
+ fun viewModelShowsContent() =
+ testScope.runTest {
+ val title by collectLastValue(underTest.title)
+ val text by collectLastValue(underTest.text)
+ contentModel =
+ exampleEnRouteContent(
+ title = "Example EnRoute Title",
+ text = "Example EnRoute Text",
+ )
+ assertThat(title).isEqualTo("Example EnRoute Title")
+ assertThat(text).isEqualTo("Example EnRoute Text")
+ }
+
+ private fun exampleEnRouteContent(
+ icon: IconModel = mock(),
+ title: CharSequence = "example text",
+ text: CharSequence = "example title",
+ ) =
+ EnRouteContentModel(
+ smallIcon = icon,
+ title = title,
+ text = text,
+ )
+}
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
new file mode 100644
index 0000000..59cfecc
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:minHeight="@*android:dimen/notification_headerless_min_height"
+ android:tag="enroute"
+ >
+
+ <include layout="@*android:layout/notification_template_material_base" />
+
+</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 02d39a4..e68da09 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -70,6 +70,7 @@
"dagger2",
"jsr330",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:msdl",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93..121577e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -342,8 +342,7 @@
// the keyguard)
if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
|| (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0
- || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0
- || (sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
+ || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
return false;
}
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 8b2449a..a8f7fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -29,6 +29,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
@@ -271,7 +272,7 @@
val intent =
Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
putExtra(
- ":settings:show_fragment_args",
+ EXTRA_SHOW_FRAGMENT_ARGUMENTS,
Bundle().apply {
putString("device_address", deviceItem.cachedBluetoothDevice.address)
}
@@ -292,7 +293,16 @@
override fun onAudioSharingButtonClicked(view: View) {
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
- startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+ val intent =
+ Intent(ACTION_AUDIO_SHARING).apply {
+ putExtra(
+ EXTRA_SHOW_FRAGMENT_ARGUMENTS,
+ Bundle().apply {
+ putBoolean(LocalBluetoothLeBroadcast.EXTRA_START_LE_AUDIO_SHARING, true)
+ }
+ )
+ }
+ startSettingsActivity(intent, view)
}
private fun cancelJob() {
@@ -320,6 +330,7 @@
companion object {
private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
private const val CONTENT_HEIGHT_PREF_KEY = Prefs.Key.BLUETOOTH_TILE_DIALOG_CONTENT_HEIGHT
+ private const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
private fun getSubtitleResId(isBluetoothEnabled: Boolean) =
if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index df50e8f..abca518 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
@@ -39,7 +40,7 @@
* being able to attempt to unlock the device.
*/
val isInputEnabled: StateFlow<Boolean>,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
private val _animateFailure = MutableStateFlow(false)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index cfd4f50..d21eccd 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -38,6 +38,7 @@
import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -79,7 +80,7 @@
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
private val flags: ComposeBouncerFlags,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
/**
* A message shown when the user has attempted the wrong credential too many times and now must
* wait a while before attempting to authenticate again.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 63b6f01..79e5f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -33,6 +33,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.assisted.AssistedFactory
@@ -62,7 +63,7 @@
private val pinViewModelFactory: PinBouncerViewModel.Factory,
private val patternViewModelFactory: PatternBouncerViewModel.Factory,
private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index a43447f..aae21b9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -66,7 +66,8 @@
@Override
public WindowInsets onApplyWindowInsets(@NonNull View view,
@NonNull WindowInsets windowInsets) {
- Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+ Insets insets = windowInsets.getInsets(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.ime());
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.leftMargin = insets.left;
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
index 46db346..208adc2 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
@@ -18,6 +18,7 @@
import android.content.pm.PackageInstaller
import android.os.Handler
+import android.text.TextUtils
import com.android.internal.annotations.GuardedBy
import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.dagger.SysUISingleton
@@ -63,6 +64,7 @@
synchronized(sessions) {
sessions.putAll(
packageInstaller.allSessions
+ .filter { !TextUtils.isEmpty(it.appPackageName) }
.map { session -> session.toModel() }
.associateBy { it.sessionId }
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 2aa6c19..98abbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -140,6 +140,10 @@
*/
val editActivityShowing: StateFlow<Boolean> = _editActivityShowing.asStateFlow()
+ private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
+
+ val selectedKey: StateFlow<String?> = _selectedKey.asStateFlow()
+
/** Whether communal features are enabled. */
val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled
@@ -179,6 +183,10 @@
}
}
+ fun setSelectedKey(key: String?) {
+ _selectedKey.value = key
+ }
+
/** Whether to show communal when exiting the occluded state. */
val showCommunalFromOccluded: Flow<Boolean> =
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -345,11 +353,10 @@
/** Show the widget editor Activity. */
fun showWidgetEditor(
- preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
) {
communalSceneInteractor.setEditModeState(EditModeState.STARTING)
- editWidgetsActivityStarter.startActivity(preselectedKey, shouldOpenWidgetPickerOnStart)
+ editWidgetsActivityStarter.startActivity(shouldOpenWidgetPickerOnStart)
}
/**
@@ -607,11 +614,6 @@
_firstVisibleItemOffset = firstVisibleItemOffset
}
- fun resetScrollPosition() {
- _firstVisibleItemIndex = 0
- _firstVisibleItemOffset = 0
- }
-
val firstVisibleItemIndex: Int
get() = _firstVisibleItemIndex
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index a0b9966..8f756a2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -88,6 +88,7 @@
keyguardState: KeyguardState? = null,
) {
applicationScope.launch("$TAG#changeScene") {
+ if (currentScene.value == newScene) return@launch
logger.logSceneChangeRequested(
from = currentScene.value,
to = newScene,
@@ -108,6 +109,7 @@
) {
applicationScope.launch("$TAG#snapToScene") {
delay(delayMillis)
+ if (currentScene.value == newScene) return@launch
logger.logSceneChangeRequested(
from = currentScene.value,
to = newScene,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 6be94a7..0929d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -55,11 +55,8 @@
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
- private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
-
/** The key of the currently selected item, or null if no item selected. */
- val selectedKey: StateFlow<String?>
- get() = _selectedKey
+ val selectedKey: StateFlow<String?> = communalInteractor.selectedKey
private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -226,7 +223,7 @@
/** Set the key of the currently selected item */
fun setSelectedKey(key: String?) {
- _selectedKey.value = key
+ communalInteractor.setSelectedKey(key)
}
/** Invoked once touches inside the lazy grid are consumed */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 4c762dc..5a39a62 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -262,7 +262,7 @@
shouldOpenWidgetPickerOnStart: Boolean,
) {
persistScrollPosition()
- communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ communalInteractor.showWidgetEditor(shouldOpenWidgetPickerOnStart)
}
override fun onDismissCtaTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 93c3a63..55a24d0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -68,7 +68,6 @@
companion object {
private const val TAG = "EditWidgetsActivity"
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
- const val EXTRA_PRESELECTED_KEY = "preselected_key"
const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
}
@@ -219,12 +218,9 @@
windowInsetsController?.hide(WindowInsets.Type.systemBars())
window.setDecorFitsSystemWindows(false)
- val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
shouldOpenWidgetPickerOnStart =
intent.getBooleanExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, false)
- communalViewModel.setSelectedKey(preselectedKey)
-
setContent {
PlatformTheme {
Box(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index af87f09..63121a8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.Intent
import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_OPEN_WIDGET_PICKER_ON_START
-import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -27,7 +26,6 @@
interface EditWidgetsActivityStarter {
fun startActivity(
- preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
)
}
@@ -39,12 +37,11 @@
private val activityStarter: ActivityStarter,
) : EditWidgetsActivityStarter {
- override fun startActivity(preselectedKey: String?, shouldOpenWidgetPickerOnStart: Boolean) {
+ override fun startActivity(shouldOpenWidgetPickerOnStart: Boolean) {
activityStarter.startActivityDismissingKeyguard(
Intent(applicationContext, EditWidgetsActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
.apply {
- preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) }
putExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, shouldOpenWidgetPickerOnStart)
},
/* onlyProvisioned = */ true,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0363a68..411cbd5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -66,6 +66,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
+import com.android.systemui.haptics.msdl.dagger.MSDLModule;
import com.android.systemui.inputmethod.InputMethodModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
@@ -231,6 +232,7 @@
MediaProjectionTaskSwitcherModule.class,
MediaRouterModule.class,
MotionToolModule.class,
+ MSDLModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 24ac542..9051745 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -255,10 +255,8 @@
return mAnimator as AnimatorSet
}
- /** Starts the dream content and dream overlay exit animations. */
- fun wakeUp() {
+ fun onWakeUp() {
cancelAnimations()
- mOverlayStateController.setExitAnimationsRunning(true)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index bf6d266..3dd2561 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -381,16 +381,17 @@
}
/**
- * Handle the dream waking up and run any necessary animations.
+ * Handle the dream waking up.
*/
- public void wakeUp() {
+ public void onWakeUp() {
+ // TODO(b/361872929): clean up this bool as it doesn't do anything anymore
// When swiping causes wakeup, do not run any animations as the dream should exit as soon
// as possible.
if (mWakingUpFromSwipe) {
return;
}
- mDreamOverlayAnimationsController.wakeUp();
+ mDreamOverlayAnimationsController.onWakeUp();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 4c22763..caf5b01 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -481,7 +481,7 @@
public void onWakeUp() {
if (mDreamOverlayContainerViewController != null) {
mDreamOverlayCallbackController.onWakeUp();
- mDreamOverlayContainerViewController.wakeUp();
+ mDreamOverlayContainerViewController.onWakeUp();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
index befd822..d547de2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -43,6 +43,8 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.lang.ref.WeakReference
+import java.util.concurrent.Executor
typealias FragmentInfoCallback = (TaskFragmentInfo) -> Unit
@@ -68,14 +70,18 @@
}
private val fragmentToken = Binder()
- private val organizer: TaskFragmentOrganizer =
- object : TaskFragmentOrganizer(executor) {
- override fun onTransactionReady(transaction: TaskFragmentTransaction) {
- handleTransactionReady(transaction)
- }
- }
- .apply { registerOrganizer(true /* isSystemOrganizer */) }
+ class Organizer(val component: WeakReference<TaskFragmentComponent>, executor: Executor) :
+ TaskFragmentOrganizer(executor) {
+ override fun onTransactionReady(transaction: TaskFragmentTransaction) {
+ component.get()?.handleTransactionReady(transaction)
+ }
+ }
+
+ private val organizer: TaskFragmentOrganizer =
+ Organizer(WeakReference(this), executor).apply {
+ registerOrganizer(true /* isSystemOrganizer */)
+ }
private fun handleTransactionReady(transaction: TaskFragmentTransaction) {
val resultT = WindowContainerTransaction()
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt
new file mode 100644
index 0000000..5ea96b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.systemui.haptics.msdl.dagger
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.google.android.msdl.domain.MSDLPlayer
+import dagger.Module
+import dagger.Provides
+
+@Module
+object MSDLModule {
+ @Provides
+ @SysUISingleton
+ fun provideMSDLPlayer(@Application context: Context): MSDLPlayer =
+ MSDLPlayer.createPlayer(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index ba533ce..362e016c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -72,6 +72,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.google.android.msdl.domain.MSDLPlayer
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -112,6 +113,7 @@
private val keyguardViewMediator: KeyguardViewMediator,
private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>,
@Main private val mainDispatcher: CoroutineDispatcher,
+ private val msdlPlayer: MSDLPlayer,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -219,6 +221,7 @@
falsingManager,
keyguardViewMediator,
mainDispatcher,
+ msdlPlayer,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
index 443e9876..208a17c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
@@ -9,3 +9,4 @@
jglazier@google.com
mpietal@google.com
tsuji@google.com
+yuandizhou@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 89851db..a7a8321 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -22,6 +22,7 @@
import android.annotation.SuppressLint
import android.graphics.Point
import android.graphics.Rect
+import android.os.VibrationAttributes
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.View
@@ -40,6 +41,7 @@
import com.android.app.tracing.coroutines.launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.systemui.Flags.msdlFeedback
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
@@ -79,6 +81,9 @@
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
import kotlin.math.min
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
@@ -112,6 +117,7 @@
falsingManager: FalsingManager?,
keyguardViewMediator: KeyguardViewMediator?,
mainImmediateDispatcher: CoroutineDispatcher,
+ msdlPlayer: MSDLPlayer?,
): DisposableHandle {
val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
@@ -351,21 +357,43 @@
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
launch {
deviceEntryHapticsInteractor.playSuccessHaptic.collect {
- vibratorHelper.performHapticFeedback(
- view,
- HapticFeedbackConstants.CONFIRM,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
- )
+ if (msdlFeedback()) {
+ val properties =
+ object : InteractionProperties {
+ override val vibrationAttributes: VibrationAttributes =
+ VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_HARDWARE_FEEDBACK
+ )
+ }
+ msdlPlayer?.playToken(MSDLToken.UNLOCK, properties)
+ } else {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ )
+ }
}
}
launch {
deviceEntryHapticsInteractor.playErrorHaptic.collect {
- vibratorHelper.performHapticFeedback(
- view,
- HapticFeedbackConstants.REJECT,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
- )
+ if (msdlFeedback()) {
+ val properties =
+ object : InteractionProperties {
+ override val vibrationAttributes: VibrationAttributes =
+ VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_HARDWARE_FEEDBACK
+ )
+ }
+ msdlPlayer?.playToken(MSDLToken.FAILURE, properties)
+ } else {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.REJECT,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 9ccfb50..f581a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -418,6 +418,7 @@
null, // falsing manager not required for preview mode
null, // keyguard view mediator is not required for preview mode
mainDispatcher,
+ null,
)
}
rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 3e3cbd0..7b0b23f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -19,7 +19,6 @@
import android.graphics.Color
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
@@ -42,7 +41,6 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val dismissCallbackRegistry: DismissCallbackRegistry,
alternateBouncerInteractor: Lazy<AlternateBouncerInteractor>,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor,
) {
// When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
private val alternateBouncerScrimAlpha = .66f
@@ -70,13 +68,10 @@
fun onRemovedFromWindow() {
statusBarKeyguardViewManager.hideAlternateBouncer(false)
- primaryBouncerInteractor.setDismissAction(null, null)
- dismissCallbackRegistry.notifyDismissCancelled()
}
fun onBackRequested() {
statusBarKeyguardViewManager.hideAlternateBouncer(false)
- primaryBouncerInteractor.setDismissAction(null, null)
dismissCallbackRegistry.notifyDismissCancelled()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 3e6dd8e..2b6c3c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
@@ -59,7 +60,7 @@
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val occlusionInteractor: SceneContainerOcclusionInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
@VisibleForTesting val clockSize = clockInteractor.clockSize
val isUdfpsVisible: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index 1314e88..6adf3e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -67,7 +67,7 @@
var leaveShadeOpen = false
return transitionAnimation.sharedFlow(
- duration = 200.milliseconds,
+ duration = 80.milliseconds,
onStart = {
leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
startAlpha = viewState.alpha()
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
deleted file mode 100644
index 03476ec..0000000
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2024 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.systemui.lifecycle
-
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.launch
-
-/**
- * A base [Activatable] with the following characteristics:
- * 1. **Can be concurrently activated by no more than one owner.** A previous call to [activate]
- * must be canceled before a new call to [activate] can be made. Trying to call [activate] while
- * already active will fail with an error
- * 2. **Can manage child [Activatable]s**. See [addChild] and [removeChild]. Added children
- * automatically track the activation state of the parent such that when the parent is active,
- * the children are active and vice-versa. Children are also retained such that deactivating the
- * parent and reactivating it also cancels and reactivates the children.
- */
-abstract class BaseActivatable : Activatable {
-
- private val _isActive = AtomicBoolean(false)
-
- var isActive: Boolean
- get() = _isActive.get()
- private set(value) {
- _isActive.set(value)
- }
-
- final override suspend fun activate(): Nothing {
- val allowed = _isActive.compareAndSet(false, true)
- check(allowed) { "Cannot activate an already active activatable!" }
-
- coroutineScope {
- try {
- launch { manageChildren() }
- onActivated()
- } finally {
- isActive = false
- }
- }
- }
-
- /**
- * Notifies that the [Activatable] has been activated.
- *
- * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
- * its state fresh and/or perform side-effects.
- *
- * The method suspends and doesn't return until all work required by the object is finished. In
- * most cases, it's expected for the work to remain ongoing forever so this method will forever
- * suspend its caller until the coroutine that called it is canceled.
- *
- * Implementations could follow this pattern:
- * ```kotlin
- * override suspend fun onActivated(): Nothing {
- * coroutineScope {
- * launch { ... }
- * launch { ... }
- * launch { ... }
- * }
- * }
- * ```
- *
- * @see activate
- */
- protected abstract suspend fun onActivated(): Nothing
-
- private val newChildren = Channel<Activatable>(Channel.BUFFERED)
- private val jobByChild: MutableMap<Activatable, Job> by lazy { mutableMapOf() }
-
- private suspend fun manageChildren(): Nothing {
- coroutineScope {
- // Reactivate children that were added during a previous activation:
- jobByChild.keys.forEach { child -> jobByChild[child] = launch { child.activate() } }
-
- // Process requests to add more children:
- newChildren.receiveAsFlow().collect { newChild ->
- removeChildInternal(newChild)
- jobByChild[newChild] = launch { newChild.activate() }
- }
-
- awaitCancellation()
- }
- }
-
- fun addChild(child: Activatable) {
- newChildren.trySend(child)
- }
-
- fun removeChild(child: Activatable) {
- removeChildInternal(child)
- }
-
- private fun removeChildInternal(child: Activatable) {
- jobByChild.remove(child)?.cancel()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt
new file mode 100644
index 0000000..0837398
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.systemui.lifecycle
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * A base [Activatable] that can only be activated by a single owner (hence "exclusive"). A previous
+ * call to [activate] must be canceled before a new call to [activate] can be made. Trying to call
+ * [activate] while already active will result in a runtime error.
+ */
+abstract class ExclusiveActivatable : Activatable {
+
+ private val _isActive = AtomicBoolean(false)
+
+ protected var isActive: Boolean
+ get() = _isActive.get()
+ private set(value) {
+ _isActive.set(value)
+ }
+
+ final override suspend fun activate(): Nothing {
+ val allowed = _isActive.compareAndSet(false, true)
+ check(allowed) { "Cannot activate an already active ExclusiveActivatable!" }
+
+ try {
+ onActivated()
+ } finally {
+ isActive = false
+ }
+ }
+
+ /**
+ * Notifies that the [Activatable] has been activated.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+ * its state fresh and/or perform side-effects.
+ *
+ * The method suspends and doesn't return until all work required by the object is finished. In
+ * most cases, it's expected for the work to remain ongoing forever so this method will forever
+ * suspend its caller until the coroutine that called it is canceled.
+ *
+ * Implementations could follow this pattern:
+ * ```kotlin
+ * override suspend fun onActivated(): Nothing {
+ * coroutineScope {
+ * launch { ... }
+ * launch { ... }
+ * launch { ... }
+ * awaitCancellation()
+ * }
+ * }
+ * ```
+ *
+ * @see activate
+ */
+ protected abstract suspend fun onActivated(): Nothing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
new file mode 100644
index 0000000..59ec2af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 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.systemui.lifecycle
+
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshots.StateFactoryMarker
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Keeps snapshot/Compose [State]s up-to-date.
+ *
+ * ```kotlin
+ * val hydrator = Hydrator()
+ * val state: Int by hydrator.hydratedStateOf(upstreamFlow)
+ *
+ * override suspend fun activate(): Nothing {
+ * hydrator.activate()
+ * }
+ * ```
+ */
+class Hydrator : ExclusiveActivatable() {
+
+ private val children = mutableListOf<Activatable>()
+
+ /**
+ * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
+ *
+ * @param source The upstream [StateFlow] to collect from; values emitted to it will be
+ * automatically set on the returned [State].
+ */
+ @StateFactoryMarker
+ fun <T> hydratedStateOf(
+ source: StateFlow<T>,
+ ): State<T> {
+ return hydratedStateOf(
+ initialValue = source.value,
+ source = source,
+ )
+ }
+
+ /**
+ * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
+ *
+ * @param initialValue The first value to place on the [State]
+ * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
+ * set on the returned [State].
+ */
+ @StateFactoryMarker
+ fun <T> hydratedStateOf(
+ initialValue: T,
+ source: Flow<T>,
+ ): State<T> {
+ check(!isActive) { "Cannot call hydratedStateOf after Hydrator is already active." }
+
+ val mutableState = mutableStateOf(initialValue)
+ children.add(
+ object : ExclusiveActivatable() {
+ override suspend fun onActivated(): Nothing {
+ source.collect { mutableState.value = it }
+ awaitCancellation()
+ }
+ }
+ )
+ return mutableState
+ }
+
+ override suspend fun onActivated() = coroutineScope {
+ children.forEach { child -> launch { child.activate() } }
+ awaitCancellation()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 979eaef..29ffcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -18,75 +18,31 @@
import android.view.View
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.snapshots.StateFactoryMarker
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
-/** Base class for all System UI view-models. */
-abstract class SysUiViewModel : BaseActivatable() {
-
- /**
- * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
- *
- * @param source The upstream [StateFlow] to collect from; values emitted to it will be
- * automatically set on the returned [State].
- */
- @StateFactoryMarker
- fun <T> hydratedStateOf(
- source: StateFlow<T>,
- ): State<T> {
- return hydratedStateOf(
- initialValue = source.value,
- source = source,
- )
- }
-
- /**
- * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
- *
- * @param initialValue The first value to place on the [State]
- * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
- * set on the returned [State].
- */
- @StateFactoryMarker
- fun <T> hydratedStateOf(
- initialValue: T,
- source: Flow<T>,
- ): State<T> {
- val mutableState = mutableStateOf(initialValue)
- addChild(
- object : BaseActivatable() {
- override suspend fun onActivated(): Nothing {
- source.collect { mutableState.value = it }
- awaitCancellation()
- }
- }
- )
- return mutableState
- }
-
- override suspend fun onActivated(): Nothing {
- awaitCancellation()
- }
-}
+/** Defines interface for all System UI view-models. */
+interface SysUiViewModel
/**
- * Returns a remembered [SysUiViewModel] of the type [T] that's automatically kept active until this
- * composable leaves the composition.
- *
- * If the [key] changes, the old [SysUiViewModel] is deactivated and a new one will be instantiated,
+ * Returns a remembered [SysUiViewModel] of the type [T]. If the returned instance is also an
+ * [Activatable], it's automatically kept active until this composable leaves the composition; if
+ * the [key] changes, the old [SysUiViewModel] is deactivated and a new one will be instantiated,
* activated, and returned.
*/
@Composable
fun <T : SysUiViewModel> rememberViewModel(
key: Any = Unit,
factory: () -> T,
-): T = rememberActivated(key, factory)
+): T {
+ val instance = remember(key) { factory() }
+ if (instance is Activatable) {
+ LaunchedEffect(instance) { instance.activate() }
+ }
+ return instance
+}
/**
* Invokes [block] in a new coroutine with a new [SysUiViewModel] that is automatically activated
@@ -100,6 +56,8 @@
): Nothing =
repeatOnWindowLifecycle(minWindowLifecycleState) {
val instance = factory()
- launch { instance.activate() }
+ if (instance is Activatable) {
+ launch { instance.activate() }
+ }
block(instance)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
new file mode 100644
index 0000000..b0abdb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for communal touch-handling logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTouchLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5cae58a..ba3c1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -618,6 +618,16 @@
}
/**
+ * Provides a {@link LogBuffer} for communal touch-handling logs.
+ */
+ @Provides
+ @SysUISingleton
+ @CommunalTouchLog
+ public static LogBuffer provideCommunalTouchLogBuffer(LogBufferFactory factory) {
+ return factory.create("CommunalTouchLog", 250);
+ }
+
+ /**
* Provides a {@link TableLogBuffer} for communal-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
index 9c29bab..ed5080d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
@@ -22,7 +22,7 @@
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -51,9 +51,8 @@
fun providesMediaDataManager(
legacyProvider: Provider<LegacyMediaDataManagerImpl>,
newProvider: Provider<MediaCarouselInteractor>,
- mediaFlags: MediaFlags,
): MediaDataManager {
- return if (mediaFlags.isSceneContainerEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
newProvider.get()
} else {
legacyProvider.get()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index adcfba7..916f8b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -73,6 +73,7 @@
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaAction
@@ -90,6 +91,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
@@ -271,7 +273,7 @@
}
override fun start() {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
@@ -1043,14 +1045,13 @@
val playOrPause =
if (isConnectingState(state.state)) {
// Spinner needs to be animating to render anything. Start it here.
- val drawable =
- context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ val drawable = MediaControlDrawables.getProgress(context)
(drawable as Animatable).start()
MediaAction(
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
+ MediaControlDrawables.getConnecting(context),
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material
)
@@ -1143,23 +1144,23 @@
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
+ MediaControlDrawables.getPlayIcon(context),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
+ MediaControlDrawables.getPlayBackground(context)
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
+ MediaControlDrawables.getPauseIcon(context),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
+ MediaControlDrawables.getPauseBackground(context)
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_prev),
+ MediaControlDrawables.getPrevIcon(context),
{ controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev),
null
@@ -1167,7 +1168,7 @@
}
PlaybackState.ACTION_SKIP_TO_NEXT -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_next),
+ MediaControlDrawables.getNextIcon(context),
{ controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next),
null
@@ -1308,7 +1309,7 @@
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container)
+ MediaControlDrawables.getPlayBackground(context)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index eab0d48..a193f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -39,6 +39,7 @@
import com.android.settingslib.media.flags.Flags
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.util.LocalMediaManagerFactory
@@ -142,6 +143,7 @@
interface Listener {
/** Called when the route has changed for a given notification. */
fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
+
/** Called when the notification was removed. */
fun onKeyRemoved(key: String, userInitiated: Boolean)
}
@@ -159,6 +161,7 @@
val token
get() = controller?.sessionToken
+
private var started = false
private var playbackType = PLAYBACK_TYPE_UNKNOWN
private var playbackVolumeControlId: String? = null
@@ -170,6 +173,7 @@
fgExecutor.execute { processDevice(key, oldKey, value) }
}
}
+
// A device that is not yet connected but is expected to connect imminently. Because it's
// expected to connect imminently, it should be displayed as the current device.
private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
@@ -354,12 +358,12 @@
activeDevice =
routingSession?.let {
- val icon = if (it.selectedRoutes.size > 1) {
- context.getDrawable(
- com.android.settingslib.R.drawable.ic_media_group_device)
- } else {
- connectedDevice?.icon // Single route. We don't change the icon.
- }
+ val icon =
+ if (it.selectedRoutes.size > 1) {
+ MediaControlDrawables.getGroupDevice(context)
+ } else {
+ connectedDevice?.icon // Single route. We don't change the icon.
+ }
// For a remote session, always use the current device from
// LocalMediaManager. Override with routing session information if
// available:
@@ -367,14 +371,16 @@
// - Icon: To show the group icon if there's more than one selected
// route.
connectedDevice?.copy(
- name = it.name ?: connectedDevice.name,
- icon = icon)
- } ?: MediaDeviceData(
- enabled = false,
- icon = context.getDrawable(R.drawable.ic_media_home_devices),
- name = context.getString(R.string.media_seamless_other_device),
- showBroadcastButton = false
- )
+ name = it.name ?: connectedDevice.name,
+ icon = icon
+ )
+ }
+ ?: MediaDeviceData(
+ enabled = false,
+ icon = MediaControlDrawables.getHomeDevices(context),
+ name = context.getString(R.string.media_seamless_other_device),
+ showBroadcastButton = false
+ )
} else {
// Prefer SASS if available when playback is local.
activeDevice = getSassDevice() ?: connectedDevice
@@ -434,10 +440,7 @@
return if (enableLeAudioSharing()) {
MediaDeviceData(
enabled = false,
- icon =
- context.getDrawable(
- com.android.settingslib.R.drawable.ic_bt_le_audio_sharing
- ),
+ icon = MediaControlDrawables.getLeAudioSharing(context),
name = context.getString(R.string.audio_sharing_description),
intent = null,
showBroadcastButton = false
@@ -445,13 +448,14 @@
} else {
MediaDeviceData(
enabled = true,
- icon = context.getDrawable(R.drawable.settings_input_antenna),
+ icon = MediaControlDrawables.getAntenna(context),
name = broadcastDescription,
intent = null,
showBroadcastButton = true
)
}
}
+
/** Return a display name for the current device / route, or null if not possible */
private fun getDeviceName(
device: MediaDevice?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 9d7160c..270ab72 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -105,7 +105,7 @@
val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia
override fun start() {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
index 28ee668..c78220e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
@@ -45,128 +45,139 @@
private var solid: Drawable? = null
fun getProgress(context: Context): Drawable? {
- return progress
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ }
+ return progress?.mutate()
?: context.getDrawable(com.android.internal.R.drawable.progress_small_material).also {
- if (!mediaControlsDrawablesReuse()) return@also
progress = it
}
}
fun getConnecting(context: Context): Drawable? {
- return connecting
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_connecting_container)
+ }
+ return connecting?.mutate()
?: context.getDrawable(R.drawable.ic_media_connecting_container).also {
- if (!mediaControlsDrawablesReuse()) return@also
connecting = it
}
}
fun getPlayIcon(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?
+ }
return playIcon?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?).also {
- if (!mediaControlsDrawablesReuse()) return@also
playIcon = it
}
}
fun getPlayBackground(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_play_container)
+ as AnimatedVectorDrawable?
+ }
return playBackground?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_play_container) as AnimatedVectorDrawable?)
- .also {
- if (!mediaControlsDrawablesReuse()) return@also
- playBackground = it
- }
+ .also { playBackground = it }
}
fun getPauseIcon(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?
+ }
return pauseIcon?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?).also {
- if (!mediaControlsDrawablesReuse()) return@also
pauseIcon = it
}
}
fun getPauseBackground(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_pause_container)
+ as AnimatedVectorDrawable?
+ }
return pauseBackground?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_pause_container) as AnimatedVectorDrawable?)
- .also {
- if (!mediaControlsDrawablesReuse()) return@also
- pauseBackground = it
- }
+ .also { pauseBackground = it }
}
fun getNextIcon(context: Context): Drawable? {
- return nextIcon
- ?: context.getDrawable(R.drawable.ic_media_next).also {
- if (!mediaControlsDrawablesReuse()) return@also
- nextIcon = it
- }
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_next)
+ }
+ return nextIcon ?: context.getDrawable(R.drawable.ic_media_next).also { nextIcon = it }
}
fun getPrevIcon(context: Context): Drawable? {
- return prevIcon
- ?: context.getDrawable(R.drawable.ic_media_prev).also {
- if (!mediaControlsDrawablesReuse()) return@also
- prevIcon = it
- }
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_prev)
+ }
+ return prevIcon ?: context.getDrawable(R.drawable.ic_media_prev).also { prevIcon = it }
}
fun getLeAudioSharing(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
+ }
return leAudioSharing
?: context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing).also {
- if (!mediaControlsDrawablesReuse()) return@also
leAudioSharing = it
}
}
fun getAntenna(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.settings_input_antenna)
+ }
return antenna
- ?: context.getDrawable(R.drawable.settings_input_antenna).also {
- if (!mediaControlsDrawablesReuse()) return@also
- antenna = it
- }
+ ?: context.getDrawable(R.drawable.settings_input_antenna).also { antenna = it }
}
fun getGroupDevice(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device)
+ }
return groupDevice
?: context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device).also {
- if (!mediaControlsDrawablesReuse()) return@also
groupDevice = it
}
}
fun getHomeDevices(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_home_devices)
+ }
return homeDevices
- ?: context.getDrawable(R.drawable.ic_media_home_devices).also {
- if (!mediaControlsDrawablesReuse()) return@also
- homeDevices = it
- }
+ ?: context.getDrawable(R.drawable.ic_media_home_devices).also { homeDevices = it }
}
fun getOutline(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.qs_media_outline_button)
+ }
return outline
- ?: context.getDrawable(R.drawable.qs_media_outline_button).also {
- if (!mediaControlsDrawablesReuse()) return@also
- outline = it
- }
+ ?: context.getDrawable(R.drawable.qs_media_outline_button).also { outline = it }
}
fun getSolid(context: Context): Drawable? {
- return solid
- ?: context.getDrawable(R.drawable.qs_media_solid_button).also {
- if (!mediaControlsDrawablesReuse()) return@also
- solid = it
- }
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.qs_media_solid_button)
+ }
+ return solid ?: context.getDrawable(R.drawable.qs_media_solid_button).also { solid = it }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index fb2bbde..19cdee7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -220,7 +220,7 @@
private val animationScaleObserver: ContentObserver =
object : ContentObserver(executor, 0) {
override fun onChange(selfChange: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
} else {
controllerById.values.forEach { it.updateAnimatorDurationScale() }
@@ -350,7 +350,7 @@
inflateSettingsButton()
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
configurationController.addCallback(configListener)
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
setUpListeners()
} else {
val visualStabilityCallback = OnReorderingAllowedListener {
@@ -391,7 +391,7 @@
listenForAnyStateToGoneKeyguardTransition(this)
listenForAnyStateToLockscreenTransition(this)
- if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle
+ if (!SceneContainerFlag.isEnabled) return@repeatOnLifecycle
listenForMediaItemsChanges(this)
}
}
@@ -733,7 +733,7 @@
when (commonViewModel) {
is MediaCommonViewModel.MediaControl -> {
val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
viewController.widthInSceneContainerPx = widthInSceneContainerPx
viewController.heightInSceneContainerPx = heightInSceneContainerPx
}
@@ -965,7 +965,7 @@
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
val newPlayer = mediaControlPanelFactory.get()
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx
newPlayer.mediaViewController.heightInSceneContainerPx =
heightInSceneContainerPx
@@ -1140,7 +1140,7 @@
}
private fun updatePlayers(recreateMedia: Boolean) {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
updateMediaPlayers(recreateMedia)
return
}
@@ -1240,7 +1240,7 @@
currentStartLocation = startLocation
currentEndLocation = endLocation
currentTransitionProgress = progress
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (mediaPlayer in MediaPlayerData.players()) {
updateViewControllerToState(mediaPlayer.mediaViewController, immediately)
}
@@ -1300,7 +1300,7 @@
/** Update listening to seekbar. */
private fun updateSeekbarListening(visibleToUser: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (player in MediaPlayerData.players()) {
player.setListening(visibleToUser && currentlyExpanded)
}
@@ -1313,7 +1313,7 @@
private fun updateCarouselDimensions() {
var width = 0
var height = 0
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (mediaPlayer in MediaPlayerData.players()) {
val controller = mediaPlayer.mediaViewController
// When transitioning the view to gone, the view gets smaller, but the translation
@@ -1405,7 +1405,7 @@
!mediaManager.hasActiveMediaOrRecommendation() &&
desiredHostState.showsOnlyActiveMedia
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (mediaPlayer in MediaPlayerData.players()) {
if (animate) {
mediaPlayer.mediaViewController.animatePendingStateChange(
@@ -1445,7 +1445,7 @@
}
fun closeGuts(immediate: Boolean = true) {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
MediaPlayerData.players().forEach { it.closeGuts(immediate) }
} else {
controllerById.values.forEach { it.closeGuts(immediate) }
@@ -1596,7 +1596,7 @@
@VisibleForTesting
fun onSwipeToDismiss() {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
mediaCarouselViewModel.onSwipeToDismiss(currentEndLocation)
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index addb014..87610cf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -111,7 +111,6 @@
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder;
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel;
import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
import com.android.systemui.media.controls.util.SmallHash;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
@@ -120,6 +119,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -209,7 +209,6 @@
static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
private final SeekBarViewModel mSeekBarViewModel;
- private final MediaFlags mMediaFlags;
private final CommunalSceneInteractor mCommunalSceneInteractor;
private SeekBarObserver mSeekBarObserver;
protected final Executor mBackgroundExecutor;
@@ -323,8 +322,7 @@
CommunalSceneInteractor communalSceneInteractor,
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDialogController broadcastDialogController,
- GlobalSettings globalSettings,
- MediaFlags mediaFlags
+ GlobalSettings globalSettings
) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
@@ -343,7 +341,6 @@
mActivityIntentHelper = activityIntentHelper;
mLockscreenUserManager = lockscreenUserManager;
mBroadcastDialogController = broadcastDialogController;
- mMediaFlags = mediaFlags;
mCommunalSceneInteractor = communalSceneInteractor;
mSeekBarViewModel.setLogSeek(() -> {
@@ -641,7 +638,7 @@
// State refresh interferes with the translation animation, only run it if it's not running.
if (!mMetadataAnimationHandler.isRunning()) {
// Don't refresh in scene framework, because it will calculate with invalid layout sizes
- if (!mMediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mMediaViewController.refreshState();
}
}
@@ -909,7 +906,7 @@
// Capture width & height from views in foreground for artwork scaling in background
int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
- if (mMediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+ if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) {
// TODO(b/312714128): ensure we have a valid size before setting background
width = mMediaViewController.getWidthInSceneContainerPx();
height = mMediaViewController.getHeightInSceneContainerPx();
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 091b886..a9d2a54 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -46,10 +46,10 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.StatusBarState
@@ -119,7 +119,6 @@
@Application private val coroutineScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
private val logger: MediaViewLogger,
- private val mediaFlags: MediaFlags,
) {
/** Track the media player setting status on lock screen. */
@@ -1111,7 +1110,7 @@
private fun updateHostAttachment() =
traceSection("MediaHierarchyManager#updateHostAttachment") {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
// No need to manage transition states - just update the desired location directly
logger.logMediaHostAttachment(desiredLocation)
mediaCarouselController.onDesiredLocationChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 584908f..e57de09 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -46,8 +46,8 @@
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.surfaceeffects.PaintDrawCallback
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
@@ -82,7 +82,6 @@
private val logger: MediaViewLogger,
private val seekBarViewModel: SeekBarViewModel,
@Main private val mainExecutor: DelayableExecutor,
- private val mediaFlags: MediaFlags,
private val globalSettings: GlobalSettings,
) {
@@ -125,7 +124,7 @@
set(value) {
if (field != value) {
field = value
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
locationChangeListener(value)
}
}
@@ -212,7 +211,7 @@
private val scrubbingChangeListener =
object : SeekBarViewModel.ScrubbingChangeListener {
override fun onScrubbingChanged(scrubbing: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
if (isScrubbing == scrubbing) return
isScrubbing = scrubbing
updateDisplayForScrubbingChange()
@@ -222,7 +221,7 @@
private val enabledChangeListener =
object : SeekBarViewModel.EnabledChangeListener {
override fun onEnabledChanged(enabled: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
if (isSeekBarEnabled == enabled) return
isSeekBarEnabled = enabled
MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
@@ -238,7 +237,7 @@
* @param listening True when player should be active. Otherwise, false.
*/
fun setListening(listening: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
seekBarViewModel.listening = listening
}
@@ -272,7 +271,7 @@
)
)
}
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
if (
this@MediaViewController::recsConfigurationChangeListener.isInitialized
) {
@@ -344,7 +343,7 @@
* Notify this controller that the view has been removed and all listeners should be destroyed
*/
fun onDestroy() {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
if (this::seekBarObserver.isInitialized) {
seekBarViewModel.progress.removeObserver(seekBarObserver)
}
@@ -565,7 +564,7 @@
state: MediaHostState?,
isGutsAnimation: Boolean = false
): TransitionViewState? {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return obtainSceneContainerViewState()
}
@@ -667,7 +666,7 @@
}
fun attachPlayer(mediaViewHolder: MediaViewHolder) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
this.mediaViewHolder = mediaViewHolder
// Setting up seek bar.
@@ -741,7 +740,7 @@
}
fun updateAnimatorDurationScale() {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
if (this::seekBarObserver.isInitialized) {
seekBarObserver.animationEnabled =
globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
@@ -801,7 +800,7 @@
}
fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
this.recommendationViewHolder = recommendationViewHolder
attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION)
@@ -810,13 +809,13 @@
}
fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
seekBarViewModel.logSeek = onSeek
onBindSeekBar(seekBarViewModel)
}
fun setUpTurbulenceNoise() {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
mediaViewHolder!!.let {
if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
turbulenceNoiseAnimationConfig =
@@ -1049,7 +1048,7 @@
*/
private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {
val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return obtainSceneContainerViewState()
}
@@ -1080,7 +1079,7 @@
/** Clear all existing measurements and refresh the state to match the view. */
fun refreshState() =
traceSection("MediaViewController#refreshState") {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
// We don't need to recreate measurements for scene container, since it's a known
// size. Just get the view state and update the layout controller
obtainSceneContainerViewState()?.let {
@@ -1169,13 +1168,13 @@
}
fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
isPrevButtonAvailable = isAvailable
prevNotVisibleValue = notVisibleValue
}
fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
isNextButtonAvailable = isAvailable
nextNotVisibleValue = notVisibleValue
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 64820e0..f460134 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
+import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaControlModel
@@ -284,9 +285,9 @@
},
cancelTextBackground =
if (model.isDismissible) {
- applicationContext.getDrawable(R.drawable.qs_media_outline_button)
+ MediaControlDrawables.getOutline(applicationContext)
} else {
- applicationContext.getDrawable(R.drawable.qs_media_solid_button)
+ MediaControlDrawables.getSolid(applicationContext)
},
onSettingsClicked = {
logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 21c3111..a65243d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -21,7 +21,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import javax.inject.Inject
@SysUISingleton
@@ -49,7 +48,4 @@
/** Check whether we allow remote media to generate resume controls */
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
-
- /** Check whether to use scene framework */
- fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 42f66cc..7d2a1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -18,7 +18,6 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -107,10 +106,7 @@
{
it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion
},
- SYSUI_STATE_COMMUNAL_HUB_SHOWING to
- {
- glanceableHubBackGesture() && it.scene == Scenes.Communal
- }
+ SYSUI_STATE_COMMUNAL_HUB_SHOWING to { it.scene == Scenes.Communal }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 3613c11..c3274b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -45,6 +45,10 @@
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.round
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -265,7 +269,7 @@
}
override fun setCollapseExpandAction(action: Runnable?) {
- // Nothing to do yet. But this should be wired to a11y
+ viewModel.collapseExpandAccessibilityAction = action
}
override fun getHeightDiff(): Int {
@@ -419,6 +423,9 @@
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
}
.padding(top = { qqsPadding })
+ .collapseExpandSemanticAction(
+ stringResource(id = R.string.accessibility_quick_settings_expand)
+ )
)
Spacer(modifier = Modifier.weight(1f))
}
@@ -428,7 +435,12 @@
private fun QuickSettingsElement() {
val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
- Column {
+ Column(
+ modifier =
+ Modifier.collapseExpandSemanticAction(
+ stringResource(id = R.string.accessibility_quick_settings_collapse)
+ )
+ ) {
Box(modifier = Modifier.fillMaxSize().weight(1f)) {
Column {
Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() })
@@ -444,6 +456,20 @@
}
}
}
+
+ private fun Modifier.collapseExpandSemanticAction(label: String): Modifier {
+ return viewModel.collapseExpandAccessibilityAction?.let {
+ semantics {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(label) {
+ it.run()
+ true
+ }
+ )
+ }
+ } ?: this
+ }
}
private fun View.setBackPressedDispatcher() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 4b1312c..df77878 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -218,6 +218,12 @@
}
.stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS)
+ /**
+ * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for
+ * determining the correct action based on the expansion state.
+ */
+ var collapseExpandAccessibilityAction: Runnable? = null
+
@AssistedFactory
interface Factory {
fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 6dc101a..b0d4fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -42,6 +42,8 @@
import javax.inject.Named
import javax.inject.Provider
import kotlin.math.max
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -145,10 +147,11 @@
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel {
val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
if (lifecycleCoroutineScope.isActive) {
- lifecycleCoroutineScope.launch {
+ lifecycleCoroutineScope.launch(start = CoroutineStart.ATOMIC) {
try {
awaitCancellation()
} finally {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 0b9cd96..9a2315b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -158,18 +158,20 @@
return item.span != 1 && offset.x > itemCenter.x
}
+@Composable
fun Modifier.dragAndDropTileSource(
sizedTile: SizedTile<EditTileViewModel>,
onTap: (TileSpec) -> Unit,
onDoubleTap: (TileSpec) -> Unit,
dragAndDropState: DragAndDropState
): Modifier {
+ val state by rememberUpdatedState(dragAndDropState)
return dragAndDropSource {
detectTapGestures(
onTap = { onTap(sizedTile.tile.tileSpec) },
onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) },
onLongPress = {
- dragAndDropState.onStarted(sizedTile)
+ state.onStarted(sizedTile)
// The tilespec from the ClipData transferred isn't actually needed as we're moving
// a tile within the same application. We're using a custom MIME type to limit the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 5f5b265..3f18fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -57,7 +57,7 @@
isActivated = modes.isNotEmpty(),
icon =
if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons())
- zenModeInteractor.getActiveModeIcon(context, modes)
+ zenModeInteractor.getActiveModeIcon(modes)
else null,
activeModes = modes.map { it.name }
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
index 55b8f5f..12f3c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
@@ -44,7 +44,7 @@
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
val mediaCarouselInteractor: MediaCarouselInteractor,
-) : SysUiViewModel() {
+) : SysUiViewModel {
val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
index abfca4b..cb99be4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
@@ -35,7 +35,7 @@
constructor(
val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : SysUiViewModel() {
+) : SysUiViewModel {
@AssistedFactory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ecf816b..a37a722 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -26,7 +26,6 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.Flags.glanceableHubBackGesture;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -86,10 +85,10 @@
import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
@@ -837,8 +836,7 @@
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
.setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
.setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
- .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING,
- glanceableHubBackGesture() && communalShowing)
+ .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING, communalShowing)
.commitUpdate(mContext.getDisplayId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 61a06db..8e2e8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -20,7 +20,6 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.lifecycle.Activatable
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
/**
@@ -36,10 +35,6 @@
/** Uniquely-identifying key for this scene. The key must be unique within its container. */
val key: SceneKey
- override suspend fun activate(): Nothing {
- awaitCancellation()
- }
-
/**
* The mapping between [UserAction] and destination [UserActionResult]s.
*
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
index b5de1b6..9144f16d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -18,6 +18,7 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,7 +33,7 @@
* need to worry about resetting the value of [actions] when the view-model is deactivated/canceled,
* this base class takes care of it.
*/
-abstract class SceneActionsViewModel : SysUiViewModel() {
+abstract class SceneActionsViewModel : SysUiViewModel, ExclusiveActivatable() {
private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index f8a9f8c..9dfb745 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -24,6 +24,8 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -32,7 +34,6 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -45,7 +46,7 @@
private val powerInteractor: PowerInteractor,
private val logger: SceneLogger,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
/**
* Keys of all scenes in the container.
*
@@ -57,8 +58,10 @@
/** The scene that should be rendered. */
val currentScene: StateFlow<SceneKey> = sceneInteractor.currentScene
+ private val hydrator = Hydrator()
+
/** Whether the container is visible. */
- val isVisible: Boolean by hydratedStateOf(sceneInteractor.isVisible)
+ val isVisible: Boolean by hydrator.hydratedStateOf(sceneInteractor.isVisible)
override suspend fun onActivated(): Nothing {
try {
@@ -75,7 +78,8 @@
}
}
)
- awaitCancellation()
+
+ hydrator.activate()
} finally {
// Clears the previously-sent MotionEventHandler so the owner of the view-model releases
// their reference to it.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
index 7f8c146..706797d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -37,7 +37,7 @@
private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
@Main private val resources: Resources,
val sliderControllerFactory: BrightnessSliderController.Factory,
-) : SysUiViewModel(), MirrorController {
+) : SysUiViewModel, MirrorController {
private val tempPosition = IntArray(2)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 181c3df..4639e22 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import android.content.Context
-import android.graphics.Insets
import android.graphics.Rect
import android.os.PowerManager
import android.os.SystemClock
@@ -26,13 +25,14 @@
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
-import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
@@ -40,7 +40,6 @@
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Flags
-import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
@@ -55,6 +54,9 @@
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalTouchLog
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -91,8 +93,10 @@
@Communal private val dataSourceDelegator: SceneDataSourceDelegator,
private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
private val keyguardMediaController: KeyguardMediaController,
- private val lockscreenSmartspaceController: LockscreenSmartspaceController
+ private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+ @CommunalTouchLog logBuffer: LogBuffer,
) : LifecycleOwner {
+ private val logger = Logger(logBuffer, "GlanceableHubContainerController")
private class CommunalWrapper(context: Context) : FrameLayout(context) {
private val consumers: MutableSet<Consumer<Boolean>> = ArraySet()
@@ -143,6 +147,17 @@
private var isTrackingHubTouch = false
/**
+ * True if a touch gesture on the lock screen has been consumed by the shade/bouncer and thus
+ * should be ignored by the hub.
+ *
+ * This is necessary on the lock screen as gestures on an empty spot go through special touch
+ * handling logic in [NotificationShadeWindowViewController] that decides if they should go to
+ * the shade or bouncer. Once the shade or bouncer are moving, we don't get the typical cancel
+ * event so to play nice, we ignore touches once we see the shade or bouncer are opening.
+ */
+ private var touchTakenByKeyguardGesture = false
+
+ /**
* True if the hub UI is fully open, meaning it should receive touch input.
*
* Tracks [CommunalInteractor.isCommunalShowing].
@@ -206,6 +221,21 @@
*/
private var isDreaming = false
+ /** Observes and logs state when the lifecycle that controls the [touchMonitor] updates. */
+ private val touchLifecycleLogger: LifecycleObserver = LifecycleEventObserver { _, event ->
+ logger.d({
+ "Touch handler lifecycle changed to $str1. hubShowing: $bool1, " +
+ "shadeShowingAndConsumingTouches: $bool2, " +
+ "anyBouncerShowing: $bool3, inEditModeTransition: $bool4"
+ }) {
+ str1 = event.toString()
+ bool1 = hubShowing
+ bool2 = shadeShowingAndConsumingTouches
+ bool3 = anyBouncerShowing
+ bool4 = inEditModeTransition
+ }
+ }
+
/** Returns a flow that tracks whether communal hub is available. */
fun communalAvailable(): Flow<Boolean> =
anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
@@ -268,6 +298,7 @@
init()
}
}
+ lifecycleRegistry.addObserver(touchLifecycleLogger)
lifecycleRegistry.currentState = Lifecycle.State.CREATED
communalContainerView = containerView
@@ -288,21 +319,13 @@
// Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
// occluded.
lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- // Avoid adding exclusion to end/start edges to allow back gestures.
- val insets =
- if (glanceableHubBackGesture()) {
- containerView.rootWindowInsets.getInsets(WindowInsets.Type.systemGestures())
- } else {
- Insets.NONE
- }
-
val ltr = containerView.layoutDirection == View.LAYOUT_DIRECTION_LTR
val backGestureInset =
Rect(
- if (ltr) 0 else insets.left,
0,
- if (ltr) insets.right else containerView.right,
+ 0,
+ if (ltr) 0 else containerView.right,
containerView.bottom,
)
@@ -318,9 +341,9 @@
// Only allow swipe up to bouncer and swipe down to shade in the very
// top/bottom to avoid conflicting with widgets in the hub grid.
Rect(
- insets.left,
+ 0,
topEdgeSwipeRegionWidth,
- containerView.right - insets.right,
+ containerView.right,
containerView.bottom - bottomEdgeSwipeRegionWidth
),
// Disable back gestures on the left side of the screen, to avoid
@@ -328,6 +351,9 @@
backGestureInset
)
}
+ logger.d({ "Insets updated: $str1" }) {
+ str1 = containerView.systemGestureExclusionRects.toString()
+ }
}
}
@@ -343,6 +369,9 @@
),
{
anyBouncerShowing = it
+ if (hubShowing) {
+ logger.d({ "New value for anyBouncerShowing: $bool1" }) { bool1 = it }
+ }
updateTouchHandlingState()
}
)
@@ -396,7 +425,13 @@
// If the shade reaches full expansion without interaction, then we should allow it
// to consume touches rather than handling it here until it disappears.
shadeShowingAndConsumingTouches =
- userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive
+ (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive).also {
+ if (it != shadeShowingAndConsumingTouches && hubShowing) {
+ logger.d({ "New value for shadeShowingAndConsumingTouches: $bool1" }) {
+ bool1 = it
+ }
+ }
+ }
updateTouchHandlingState()
}
)
@@ -404,6 +439,7 @@
communalContainerWrapper = CommunalWrapper(containerView.context)
communalContainerWrapper?.addView(communalContainerView)
+ logger.d("Hub container initialized")
return communalContainerWrapper!!
}
@@ -446,6 +482,10 @@
(it.parent as ViewGroup).removeView(it)
communalContainerWrapper = null
}
+
+ lifecycleRegistry.removeObserver(touchLifecycleLogger)
+
+ logger.d("Hub container disposed")
}
/**
@@ -463,15 +503,20 @@
// In the case that we are handling full swipes on the lockscreen, are on the lockscreen,
// and the touch is within the horizontal notification band on the screen, do not process
// the touch.
- if (
- !hubShowing &&
- (!notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) ||
- keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) ||
- lockscreenSmartspaceController.isWithinSmartspaceBounds(
- ev.x.toInt(),
- ev.y.toInt()
- ))
- ) {
+ val touchOnNotifications =
+ !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y)
+ val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
+ val touchOnSmartspace =
+ lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
+ if (!hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace)) {
+ logger.d({
+ "Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
+ "touchOnSmartspace: $bool3"
+ }) {
+ bool1 = touchOnNotifications
+ bool2 = touchOnUmo
+ bool3 = touchOnSmartspace
+ }
return false
}
@@ -487,12 +532,56 @@
val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches
if ((isDown || isMove) && !hubOccluded) {
+ if (isDown) {
+ logger.d({
+ "Touch started. x: $int1, y: $int2, hubShowing: $bool1, isDreaming: $bool2, " +
+ "onLockscreen: $bool3"
+ }) {
+ int1 = ev.x.toInt()
+ int2 = ev.y.toInt()
+ bool1 = hubShowing
+ bool2 = isDreaming
+ bool3 = onLockscreen
+ }
+ }
isTrackingHubTouch = true
}
if (isTrackingHubTouch) {
+ // On the lock screen, our touch handlers are not active and we rely on the NSWVC's
+ // touch handling for gestures on blank areas, which can go up to show the bouncer or
+ // down to show the notification shade. We see the touches first and they are not
+ // consumed and cancelled like on the dream or hub so we have to gracefully ignore them
+ // if the shade or bouncer are handling them. This issue only applies to touches on the
+ // keyguard itself, once the bouncer or shade are fully open, our logic stops us from
+ // taking touches.
+ touchTakenByKeyguardGesture =
+ (onLockscreen && (shadeConsumingTouches || anyBouncerShowing)).also {
+ if (it != touchTakenByKeyguardGesture && it) {
+ logger.d(
+ "Lock screen touch consumed by shade or bouncer, ignoring " +
+ "subsequent touches"
+ )
+ }
+ }
if (isUp || isCancel) {
+ logger.d({
+ val endReason = if (bool1) "up" else "cancel"
+ "Touch ended with $endReason. x: $int1, y: $int2, " +
+ "shadeConsumingTouches: $bool2, anyBouncerShowing: $bool3"
+ }) {
+ int1 = ev.x.toInt()
+ int2 = ev.y.toInt()
+ bool1 = isUp
+ bool2 = shadeConsumingTouches
+ bool3 = anyBouncerShowing
+ }
isTrackingHubTouch = false
+
+ // Clear out touch taken state to ensure the up/cancel event still gets dispatched
+ // to the hub. This is necessary as the hub always receives at least the initial
+ // down even if the shade or bouncer end up handling the touch.
+ touchTakenByKeyguardGesture = false
}
return dispatchTouchEvent(ev)
}
@@ -513,21 +602,8 @@
return true
}
try {
- // On the lock screen, our touch handlers are not active and we rely on the NSWVC's
- // touch handling for gestures on blank areas, which can go up to show the bouncer or
- // down to show the notification shade. We see the touches first and they are not
- // consumed and cancelled like on the dream or hub so we have to gracefully ignore them
- // if the shade or bouncer are handling them. This issue only applies to touches on the
- // keyguard itself, once the bouncer or shade are fully open, our logic stops us from
- // taking touches.
- val touchTaken = onLockscreen && (shadeConsumingTouches || anyBouncerShowing)
-
- // Only dispatch touches to communal if not already handled or the touch is ending,
- // meaning the event is an up or cancel. This is necessary as the hub always receives at
- // least the initial down even if the shade or bouncer end up handling the touch.
- val dispatchToCommunal = !touchTaken || !isTrackingHubTouch
var handled = false
- if (dispatchToCommunal) {
+ if (!touchTakenByKeyguardGesture) {
communalContainerWrapper?.dispatchTouchEvent(ev) {
if (it) {
handled = true
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index 00c0235..25ae44e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
@@ -37,7 +38,7 @@
class OverlayShadeViewModel
@AssistedInject
constructor(private val sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor) :
- SysUiViewModel() {
+ SysUiViewModel, ExclusiveActivatable() {
private val _backgroundScene = MutableStateFlow(Scenes.Lockscreen)
/** The scene to show in the background when the overlay shade is open. */
val backgroundScene: StateFlow<SceneKey> = _backgroundScene.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index f0e9d41..edfe79a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -24,6 +24,7 @@
import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
@@ -65,7 +66,7 @@
private val privacyChipInteractor: PrivacyChipInteractor,
private val clockInteractor: ShadeHeaderClockInteractor,
private val broadcastDispatcher: BroadcastDispatcher,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
/** True if there is exactly one mobile connection. */
val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index fe3bcb5..f0f2a65 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -20,6 +20,7 @@
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
@@ -61,7 +62,7 @@
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val sceneInteractor: SceneInteractor,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 3068460..6eadd26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -471,17 +471,7 @@
*/
private Drawable getIcon(Context sysuiContext,
Context context, StatusBarIcon statusBarIcon) {
- int userId = statusBarIcon.user.getIdentifier();
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- }
-
- // Try to load the monochrome app icon if applicable
- Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
- // Otherwise, just use the icon normally
- if (icon == null) {
- icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
- }
+ Drawable icon = loadDrawable(context, statusBarIcon);
TypedValue typedValue = new TypedValue();
sysuiContext.getResources().getValue(R.dimen.status_bar_icon_scale_factor,
@@ -509,6 +499,26 @@
}
@Nullable
+ private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) {
+ if (usesModeIcons() && statusBarIcon.preloadedIcon != null) {
+ return statusBarIcon.preloadedIcon.mutate();
+ } else {
+ int userId = statusBarIcon.user.getIdentifier();
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_SYSTEM;
+ }
+
+ // Try to load the monochrome app icon if applicable
+ Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
+ // Otherwise, just use the icon normally
+ if (icon == null) {
+ icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
+ }
+ return icon;
+ }
+ }
+
+ @Nullable
private Drawable maybeGetMonochromeAppIcon(Context context,
StatusBarIcon statusBarIcon) {
if (android.app.Flags.notificationsUseMonochromeAppIcon()
@@ -1020,4 +1030,9 @@
public boolean showsConversation() {
return mShowsConversation;
}
+
+ private static boolean usesModeIcons() {
+ return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+ && android.app.Flags.modesUiIcons();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index f74c9a6..e9292f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -79,6 +79,7 @@
// NOTE: NotificationEntry.isClearable will internally check group children to ensure
// the group itself definitively clearable.
val isClearable = !isSensitiveContentProtectionActive && entry.isClearable
+ && !entry.isSensitive.value
when {
isSilent && isClearable -> hasClearableSilentNotifs = true
isSilent && !isClearable -> hasNonClearableSilentNotifs = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index bf5b3a3..da29b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -22,6 +22,7 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
import com.android.systemui.statusbar.notification.row.shared.IconModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
@@ -68,12 +69,13 @@
builder: Notification.Builder,
systemUIContext: Context,
packageContext: Context
- ): RichOngoingContentModel? =
+ ): RichOngoingContentModel? {
+ val sbn = entry.sbn
+ val notification = sbn.notification
+ val icon = IconModel(notification.smallIcon)
+
try {
- val sbn = entry.sbn
- val notification = sbn.notification
- val icon = IconModel(notification.smallIcon)
- if (sbn.packageName == "com.google.android.deskclock") {
+ return if (sbn.packageName == "com.google.android.deskclock") {
when (notification.channelId) {
"Timers v2" -> {
parseTimerNotification(notification, icon)
@@ -87,11 +89,14 @@
null
}
}
+ } else if (builder.style is Notification.EnRouteStyle) {
+ parseEnRouteNotification(notification, icon)
} else null
} catch (e: Exception) {
Log.e("RONs", "Error parsing RON", e)
- null
+ return null
}
+ }
/**
* FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
@@ -199,4 +204,15 @@
.plusMinutes(minute.toLong())
.plusSeconds(second.toLong())
}
+
+ private fun parseEnRouteNotification(
+ notification: Notification,
+ icon: IconModel,
+ ): EnRouteContentModel {
+ return EnRouteContentModel(
+ smallIcon = icon,
+ title = notification.extras.getCharSequence(Notification.EXTRA_TITLE),
+ text = notification.extras.getCharSequence(Notification.EXTRA_TEXT),
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
index 828fc21..2c462b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
@@ -27,12 +27,15 @@
import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel
import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
+import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
import com.android.systemui.statusbar.notification.row.ui.view.TimerView
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent
import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
import javax.inject.Inject
@@ -119,7 +122,15 @@
parentView,
viewType
)
- is StopwatchContentModel -> TODO("Not yet implemented")
+ is EnRouteContentModel ->
+ inflateEnRouteView(
+ existingView,
+ component::createEnRouteViewModel,
+ systemUiContext,
+ parentView,
+ viewType
+ )
+ else -> TODO("Not yet implemented")
}
}
@@ -131,7 +142,8 @@
if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
return when (contentModel) {
is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
- is StopwatchContentModel -> TODO("Not yet implemented")
+ is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType)
+ else -> TODO("Not yet implemented")
}
}
@@ -167,4 +179,38 @@
existingView: View?,
viewType: RichOngoingNotificationViewType
): Boolean = true
+
+ private fun inflateEnRouteView(
+ existingView: View?,
+ createViewModel: () -> EnRouteViewModel,
+ systemUiContext: Context,
+ parentView: ViewGroup,
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (existingView is EnRouteView && !existingView.isReinflateNeeded())
+ return KeepExistingView
+ return when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> {
+ val newView =
+ LayoutInflater.from(systemUiContext)
+ .inflate(
+ R.layout.notification_template_en_route_contracted,
+ parentView,
+ /* attachToRoot= */ false
+ ) as EnRouteView
+
+ InflatedContentViewHolder(newView) {
+ EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
+ }
+ }
+ RichOngoingNotificationViewType.Expanded,
+ RichOngoingNotificationViewType.HeadsUp -> NullContentView
+ }
+ }
+
+ private fun canKeepEnRouteView(
+ contentModel: EnRouteContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
index 4705ace..72823a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row.domain.interactor
import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -26,4 +27,8 @@
/** Content of a rich ongoing timer notification. */
val timerContentModel: Flow<TimerContentModel> =
repository.richOngoingContentModel.filterIsInstance<TimerContentModel>()
+
+ /** Content of a rich ongoing timer notification. */
+ val enRouteContentModel: Flow<EnRouteContentModel> =
+ repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
new file mode 100644
index 0000000..7e78cca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.row.shared
+
+/**
+ * Represents something en route.
+ *
+ * @param smallIcon the main small icon of the EnRoute notification.
+ * @param title the title of the EnRoute notification.
+ * @param text the text of the EnRoute notification.
+ */
+data class EnRouteContentModel(
+ val smallIcon: IconModel,
+ val title: CharSequence?,
+ val text: CharSequence?,
+) : RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
new file mode 100644
index 0000000..e5c2b5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.row.ui.view
+
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.internal.R
+import com.android.internal.widget.NotificationExpandButton
+
+class EnRouteView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+ private val configTracker = ConfigurationTracker(resources)
+
+ private lateinit var icon: ImageView
+ private lateinit var title: TextView
+ private lateinit var text: TextView
+ private lateinit var expandButton: NotificationExpandButton
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ icon = requireViewById(R.id.icon)
+ title = requireViewById(R.id.title)
+ text = requireViewById(R.id.text)
+
+ expandButton = requireViewById(R.id.expand_button)
+ expandButton.setExpanded(false)
+ }
+
+ /** the resources configuration has changed such that the view needs to be reinflated */
+ fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
+
+ fun setIcon(icon: Icon?) {
+ this.icon.setImageIcon(icon)
+ }
+
+ fun setTitle(title: CharSequence?) {
+ this.title.text = title
+ }
+
+ fun setText(text: CharSequence?) {
+ this.text.text = text
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
new file mode 100644
index 0000000..3b8957c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.row.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [EnRouteView] to its [view model][EnRouteViewModel]. */
+object EnRouteViewBinder {
+ fun bindWhileAttached(
+ view: EnRouteView,
+ viewModel: EnRouteViewModel,
+ ): DisposableHandle {
+ return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
+ }
+
+ suspend fun bind(
+ view: EnRouteView,
+ viewModel: EnRouteViewModel,
+ ) = coroutineScope {
+ launch { viewModel.icon.collect { view.setIcon(it) } }
+ launch { viewModel.title.collect { view.setTitle(it) } }
+ launch { viewModel.text.collect { view.setText(it) } }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
new file mode 100644
index 0000000..307a983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.graphics.drawable.Icon
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
+import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+
+/** A view model for EnRoute notifications. */
+class EnRouteViewModel
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ rowInteractor: NotificationRowInteractor,
+) : FlowDumperImpl(dumpManager) {
+ init {
+ /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
+ }
+
+ val icon: Flow<Icon?> = rowInteractor.enRouteContentModel.mapNotNull { it.smallIcon.icon }
+
+ val title: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.title }
+
+ val text: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.text }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
index dad52a3..5552d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
@@ -33,4 +33,6 @@
}
fun createTimerViewModel(): TimerViewModel
+
+ fun createEnRouteViewModel(): EnRouteViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 0e984cf..b2045fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -17,10 +17,13 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.compose.animation.scene.ObservableTransitionState.Transition
+import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeCurrentScene
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -60,42 +63,47 @@
keyguardInteractor: Lazy<KeyguardInteractor>,
) :
ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
- SysUiViewModel() {
+ SysUiViewModel,
+ ExclusiveActivatable() {
override suspend fun onActivated(): Nothing {
activateFlowDumper()
}
- private fun expandFractionForScene(scene: SceneKey, shadeExpansion: Float): Float =
- when (scene) {
+ private fun expandedInScene(scene: SceneKey): Boolean {
+ return when (scene) {
Scenes.Lockscreen,
- Scenes.QuickSettings -> 1f
- else -> shadeExpansion
+ Scenes.Shade,
+ Scenes.QuickSettings -> true
+ else -> false
}
+ }
- private fun expandFractionForTransition(
- state: ObservableTransitionState.Transition.ChangeCurrentScene,
+ private fun fullyExpandedDuringSceneChange(change: ChangeCurrentScene): Boolean {
+ // The lockscreen stack is visible during all transitions away from the lockscreen, so keep
+ // the stack expanded until those transitions finish.
+ return (expandedInScene(change.fromScene) && expandedInScene(change.toScene)) ||
+ change.isBetween({ it == Scenes.Lockscreen }, { true })
+ }
+
+ private fun expandFractionDuringSceneChange(
+ change: ChangeCurrentScene,
shadeExpansion: Float,
- shadeMode: ShadeMode,
qsExpansion: Float,
- quickSettingsScene: SceneKey
- ): Float =
- if (
- state.isBetween({ it == Scenes.Lockscreen }, { it in SceneFamilies.NotifShade }) ||
- state.isBetween({ it in SceneFamilies.NotifShade }, { it == quickSettingsScene })
- ) {
+ ): Float {
+ return if (fullyExpandedDuringSceneChange(change)) {
1f
- } else if (
- shadeMode != ShadeMode.Split &&
- state.isBetween({ it in SceneFamilies.Home }, { it == quickSettingsScene })
- ) {
+ } else if (change.isBetween({ it == Scenes.Gone }, { it in SceneFamilies.NotifShade })) {
+ shadeExpansion
+ } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
// during QS expansion, increase fraction at same rate as scrim alpha,
// but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
(qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
.coerceIn(0f, 1f)
} else {
- shadeExpansion
+ 0f
}
+ }
/**
* The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
@@ -109,21 +117,17 @@
shadeInteractor.qsExpansion,
sceneInteractor.transitionState,
sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
- ) { shadeExpansion, shadeMode, qsExpansion, transitionState, quickSettingsScene ->
+ ) { shadeExpansion, _, qsExpansion, transitionState, _ ->
when (transitionState) {
- is ObservableTransitionState.Idle ->
- expandFractionForScene(transitionState.currentScene, shadeExpansion)
- is ObservableTransitionState.Transition.ChangeCurrentScene ->
- expandFractionForTransition(
+ is Idle -> if (expandedInScene(transitionState.currentScene)) 1f else 0f
+ is ChangeCurrentScene ->
+ expandFractionDuringSceneChange(
transitionState,
shadeExpansion,
- shadeMode,
qsExpansion,
- quickSettingsScene
)
- is ObservableTransitionState.Transition.ShowOrHideOverlay,
- is ObservableTransitionState.Transition.ReplaceOverlay ->
- TODO("b/359173565: Handle overlay transitions")
+ is Transition.ShowOrHideOverlay,
+ is Transition.ReplaceOverlay -> TODO("b/359173565: Handle overlay transitions")
}
}
.distinctUntilChanged()
@@ -134,9 +138,7 @@
val shouldResetStackTop: Flow<Boolean> =
sceneInteractor.transitionState
- .mapNotNull { state ->
- state is ObservableTransitionState.Idle && state.currentScene == Scenes.Gone
- }
+ .mapNotNull { state -> state is Idle && state.currentScene == Scenes.Gone }
.distinctUntilChanged()
.dumpWhileCollecting("shouldResetStackTop")
@@ -241,7 +243,7 @@
}
}
-private fun ObservableTransitionState.Transition.ChangeCurrentScene.isBetween(
+private fun ChangeCurrentScene.isBetween(
a: (SceneKey) -> Boolean,
b: (SceneKey) -> Boolean
): Boolean = (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index ffa1de7..d891f62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -52,7 +53,8 @@
featureFlags: FeatureFlagsClassic,
dumpManager: DumpManager,
) :
- SysUiViewModel(),
+ SysUiViewModel,
+ ExclusiveActivatable(),
ActivatableFlowDumper by ActivatableFlowDumperImpl(
dumpManager = dumpManager,
tag = "NotificationsPlaceholderViewModel",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3dd265b..e3242d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2365,9 +2365,14 @@
// lock screen where users can use the UDFPS affordance to enter the device
mStatusBarKeyguardViewManager.reset(true);
} else if (mState == StatusBarState.KEYGUARD
- && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
- && mStatusBarKeyguardViewManager.isSecure()) {
- if (!relockWithPowerButtonImmediately()) {
+ && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
+ boolean needsBouncer = mStatusBarKeyguardViewManager.isSecure();
+ if (relockWithPowerButtonImmediately()) {
+ // Only request if SIM bouncer is needed
+ needsBouncer = mStatusBarKeyguardViewManager.needsFullscreenBouncer();
+ }
+
+ if (needsBouncer) {
Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
if (SceneContainerFlag.isEnabled()) {
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 3ba62b1..d1b5160 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -44,6 +44,8 @@
import androidx.lifecycle.Observer;
+import com.android.settingslib.notification.modes.ZenIconLoader;
+import com.android.settingslib.notification.modes.ZenMode;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -78,6 +80,7 @@
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.DateFormatUtil;
@@ -99,7 +102,6 @@
CommandQueue.Callbacks,
RotationLockControllerCallback,
Listener,
- ZenModeController.Callback,
DeviceProvisionedListener,
KeyguardStateController.Callback,
PrivacyItemController.Callback,
@@ -161,6 +163,7 @@
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
private final PrivacyLogger mPrivacyLogger;
+ private final ZenModeInteractor mZenModeInteractor;
private boolean mZenVisible;
private boolean mVibrateVisible;
@@ -193,6 +196,7 @@
PrivacyItemController privacyItemController,
PrivacyLogger privacyLogger,
ConnectedDisplayInteractor connectedDisplayInteractor,
+ ZenModeInteractor zenModeInteractor,
JavaAdapter javaAdapter
) {
mIconController = iconController;
@@ -224,6 +228,7 @@
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
mPrivacyLogger = privacyLogger;
+ mZenModeInteractor = zenModeInteractor;
mJavaAdapter = javaAdapter;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
@@ -355,7 +360,13 @@
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
- mZenController.addCallback(this);
+ if (usesModeIcons()) {
+ // Note that we're not fully replacing ZenModeController with ZenModeInteractor, so
+ // we listen for the extra event here but still add the ZMC callback.
+ mJavaAdapter.alwaysCollectFlow(mZenModeInteractor.getMainActiveMode(),
+ this::onActiveModeChanged);
+ }
+ mZenController.addCallback(mZenControllerCallback);
if (!Flags.statusBarScreenSharingChips()) {
// If the flag is enabled, the cast icon is handled in the new screen sharing chips
// instead of here so we don't need to listen for events here.
@@ -385,15 +396,44 @@
() -> mResources.getString(R.string.accessibility_managed_profile));
}
- @Override
- public void onZenChanged(int zen) {
- updateVolumeZen();
+ private void onActiveModeChanged(@Nullable ZenMode mode) {
+ if (!usesModeIcons()) {
+ Log.wtf(TAG, "onActiveModeChanged shouldn't be called if MODES_UI_ICONS is disabled");
+ return;
+ }
+ boolean visible = mode != null;
+ if (visible) {
+ // TODO: b/360399800 - Get the resource id, package, and cached drawable from the mode;
+ // this is a shortcut for testing (there should be no direct dependency on
+ // ZenIconLoader here).
+ String resPackage = mode.isSystemOwned() ? null : mode.getRule().getPackageName();
+ int iconResId = mode.getRule().getIconResId();
+ if (iconResId == 0) {
+ iconResId = ZenIconLoader.getIconResourceIdFromType(mode.getType());
+ }
+
+ mIconController.setResourceIcon(mSlotZen, resPackage, iconResId,
+ /* preloadedIcon= */ null, mode.getName());
+ }
+ if (visible != mZenVisible) {
+ mIconController.setIconVisibility(mSlotZen, visible);
+ mZenVisible = visible;
+ }
}
- @Override
- public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
- updateVolumeZen();
- }
+ // TODO: b/308591859 - Should be removed and use the ZenModeInteractor only.
+ private final ZenModeController.Callback mZenControllerCallback =
+ new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ updateVolumeZen();
+ }
+
+ @Override
+ public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
+ updateVolumeZen();
+ }
+ };
private void updateAlarm() {
final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
@@ -417,15 +457,24 @@
return mResources.getString(R.string.accessibility_quick_settings_alarm, dateString);
}
- private final void updateVolumeZen() {
+ private void updateVolumeZen() {
+ int zen = mZenController.getZen();
+ if (!usesModeIcons()) {
+ updateZenIcon(zen);
+ }
+ updateRingerAndAlarmIcons(zen);
+ }
+
+ private void updateZenIcon(int zen) {
+ if (usesModeIcons()) {
+ Log.wtf(TAG, "updateZenIcon shouldn't be called if MODES_UI_ICONS is enabled");
+ return;
+ }
+
boolean zenVisible = false;
int zenIconId = 0;
String zenDescription = null;
- boolean vibrateVisible = false;
- boolean muteVisible = false;
- int zen = mZenController.getZen();
-
if (DndTile.isVisible(mSharedPreferences) || DndTile.isCombinedIcon(mSharedPreferences)) {
zenVisible = zen != Global.ZEN_MODE_OFF;
zenIconId = R.drawable.stat_sys_dnd;
@@ -440,7 +489,21 @@
zenDescription = mResources.getString(R.string.interruption_level_priority);
}
- if (!ZenModeConfig.isZenOverridingRinger(zen, mZenController.getConsolidatedPolicy())) {
+ if (zenVisible) {
+ mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
+ }
+ if (zenVisible != mZenVisible) {
+ mIconController.setIconVisibility(mSlotZen, zenVisible);
+ mZenVisible = zenVisible;
+ }
+ }
+
+ private void updateRingerAndAlarmIcons(int zen) {
+ boolean vibrateVisible = false;
+ boolean muteVisible = false;
+
+ NotificationManager.Policy consolidatedPolicy = mZenController.getConsolidatedPolicy();
+ if (!ZenModeConfig.isZenOverridingRinger(zen, consolidatedPolicy)) {
final Integer ringerModeInternal =
mRingerModeTracker.getRingerModeInternal().getValue();
if (ringerModeInternal != null) {
@@ -452,14 +515,6 @@
}
}
- if (zenVisible) {
- mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
- }
- if (zenVisible != mZenVisible) {
- mIconController.setIconVisibility(mSlotZen, zenVisible);
- mZenVisible = zenVisible;
- }
-
if (vibrateVisible != mVibrateVisible) {
mIconController.setIconVisibility(mSlotVibrate, vibrateVisible);
mVibrateVisible = vibrateVisible;
@@ -888,4 +943,9 @@
mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
}
+
+ private static boolean usesModeIcons() {
+ return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+ && android.app.Flags.modesUiIcons();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0f93ff2..f11fd7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -68,7 +68,6 @@
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
@@ -104,6 +103,7 @@
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import dagger.Lazy;
@@ -179,6 +179,8 @@
private float mFraction = -1f;
private boolean mTracking = false;
private boolean mBouncerShowingOverDream;
+ private int mAttemptsToShowBouncer = 0;
+ private DelayableExecutor mExecutor;
private final PrimaryBouncerExpansionCallback mExpansionCallback =
new PrimaryBouncerExpansionCallback() {
@@ -315,8 +317,6 @@
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
- private FeatureFlags mFlags;
-
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsBackAnimationEnabled;
private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@@ -399,9 +399,11 @@
JavaAdapter javaAdapter,
Lazy<SceneInteractor> sceneInteractorLazy,
StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor,
+ @Main DelayableExecutor executor,
Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy
) {
mContext = context;
+ mExecutor = executor;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
mConfigurationController = configurationController;
@@ -711,13 +713,7 @@
* {@link #needsFullscreenBouncer()}.
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
- boolean isDozing = mDozing;
- if (Flags.simPinRaceConditionOnRestart()) {
- KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
- .getTo();
- isDozing = mDozing || toState == KeyguardState.DOZING || toState == KeyguardState.AOD;
- }
- if (needsFullscreenBouncer() && !isDozing) {
+ if (needsFullscreenBouncer() && !mDozing) {
// The keyguard might be showing (already). So we need to hide it.
if (!primaryBouncerIsShowing()) {
if (SceneContainerFlag.isEnabled()) {
@@ -727,9 +723,22 @@
} else {
if (Flags.simPinRaceConditionOnRestart()) {
if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true)) {
+ mAttemptsToShowBouncer = 0;
mCentralSurfaces.hideKeyguard();
} else {
- mCentralSurfaces.showKeyguard();
+ if (mAttemptsToShowBouncer > 6) {
+ mAttemptsToShowBouncer = 0;
+ Log.e(TAG, "Too many failed attempts to show bouncer, showing "
+ + "keyguard instead");
+ mCentralSurfaces.showKeyguard();
+ } else {
+ Log.v(TAG, "Failed to show bouncer, attempt #: "
+ + mAttemptsToShowBouncer++);
+ mExecutor.executeDelayed(() ->
+ showBouncerOrKeyguard(hideBouncerWhenShowing,
+ isFalsingReset),
+ 500);
+ }
}
} else {
mCentralSurfaces.hideKeyguard();
@@ -1874,6 +1883,11 @@
|| mode == KeyguardSecurityModel.SecurityMode.SimPuk;
}
+ @VisibleForTesting
+ void setAttemptsToShowBouncer(int attempts) {
+ mAttemptsToShowBouncer = attempts;
+ }
+
/**
* Delegate used to send show and hide events to an alternate authentication method instead of
* the regular pin/pattern/password bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
index 1ada30e..ee528e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
@@ -18,9 +18,12 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.ArraySet;
+import androidx.annotation.DrawableRes;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
@@ -58,6 +61,18 @@
void setIcon(String slot, int resourceId, CharSequence contentDescription);
/**
+ * Adds or updates an icon for the given slot.
+ *
+ * @param resPackage the package name containing the resource in question. Can be null if the
+ * icon is a system icon (e.g. a resource from {@code android.R.drawable} or
+ * {@code com.android.internal.R.drawable}).
+ * @param iconResId id of the drawable resource
+ * @param preloadedIcon optional drawable corresponding to {@code iconResId}, if known
+ */
+ void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId,
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription);
+
+ /**
* Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
* set up (inflated and added to the view hierarchy).
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
index 85213cb..ad3a9e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
@@ -18,16 +18,22 @@
import static com.android.systemui.statusbar.phone.ui.StatusBarIconList.Slot;
+import static com.google.common.base.Preconditions.checkArgument;
+
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.ViewGroup;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
@@ -221,19 +227,66 @@
}
}
- /** */
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
+ setResourceIconInternal(
+ slot,
+ Icon.createWithResource(mContext, resourceId),
+ /* preloadedIcon= */ null,
+ contentDescription,
+ StatusBarIcon.Type.SystemIcon);
+ }
+
+ @Override
+ public void setResourceIcon(String slot, @Nullable String resPackage,
+ @DrawableRes int iconResId, @Nullable Drawable preloadedIcon,
+ CharSequence contentDescription) {
+ if (!usesModeIcons()) {
+ Log.wtf("TAG",
+ "StatusBarIconController.setResourceIcon() should not be called without "
+ + "MODES_UI & MODES_UI_ICONS!");
+ // Fall back to old implementation, although it will not load the icon if it's from a
+ // different package.
+ setIcon(slot, iconResId, contentDescription);
+ return;
+ }
+
+ Icon icon = resPackage != null
+ ? Icon.createWithResource(resPackage, iconResId)
+ : Icon.createWithResource(mContext, iconResId);
+
+ setResourceIconInternal(
+ slot,
+ icon,
+ preloadedIcon,
+ contentDescription,
+ StatusBarIcon.Type.ResourceIcon);
+ }
+
+ private void setResourceIconInternal(String slot, Icon resourceIcon,
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription,
+ StatusBarIcon.Type type) {
+ checkArgument(resourceIcon.getType() == Icon.TYPE_RESOURCE,
+ "Expected Icon of TYPE_RESOURCE, but got " + resourceIcon.getType());
+ String resPackage = resourceIcon.getResPackage();
+ if (TextUtils.isEmpty(resPackage)) {
+ resPackage = mContext.getPackageName();
+ }
+
StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
if (holder == null) {
- StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
- Icon.createWithResource(mContext, resourceId), 0, 0,
- contentDescription, StatusBarIcon.Type.SystemIcon);
+ StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, resPackage,
+ resourceIcon, /* iconLevel= */ 0, /* number=*/ 0,
+ contentDescription, type);
+ icon.preloadedIcon = preloadedIcon;
holder = StatusBarIconHolder.fromIcon(icon);
setIcon(slot, holder);
} else {
- holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
+ holder.getIcon().pkg = resPackage;
+ holder.getIcon().icon = resourceIcon;
holder.getIcon().contentDescription = contentDescription;
+ holder.getIcon().type = type;
+ holder.getIcon().preloadedIcon = preloadedIcon;
handleSet(slot, holder);
}
}
@@ -524,4 +577,9 @@
return slot + EXTERNAL_SLOT_SUFFIX;
}
}
+
+ private static boolean usesModeIcons() {
+ return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+ && android.app.Flags.modesUiIcons();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index f16fcb5..d351da6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -95,10 +95,6 @@
return mode.getIcon(context, iconLoader).await().asIcon()
}
- suspend fun getLockscreenModeIcon(mode: ZenMode): Icon {
- return mode.getLockscreenIcon(context, iconLoader).await().asIcon()
- }
-
/**
* Given the list of modes (which may include zero or more currently active modes), returns an
* icon representing the active mode, if any (or, if multiple modes are active, to the most
@@ -106,8 +102,8 @@
* standard DND icon for implicit modes, instead of the launcher icon of the associated
* package).
*/
- suspend fun getActiveModeIcon(context: Context, modes: List<ZenMode>): Icon? {
- return getMainActiveMode(modes)?.let { m -> getLockscreenModeIcon(m) }
+ suspend fun getActiveModeIcon(modes: List<ZenMode>): Icon? {
+ return getMainActiveMode(modes)?.let { m -> getModeIcon(m) }
}
fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
index ae0061b..727e51f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -19,7 +19,7 @@
import android.util.IndentingPrintWriter
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
-import com.android.systemui.lifecycle.BaseActivatable
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
@@ -189,7 +189,7 @@
) : SimpleFlowDumper(), ActivatableFlowDumper {
private val registration =
- object : BaseActivatable() {
+ object : ExclusiveActivatable() {
override suspend fun onActivated(): Nothing {
try {
dumpManager.registerCriticalDumpable(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 2021400..664a0bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -21,7 +21,6 @@
import androidx.test.filters.SmallTest
import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -31,7 +30,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -64,31 +62,13 @@
@Test
fun onRemovedFromWindow() =
testScope.runTest {
- kosmos.primaryBouncerInteractor.setDismissAction(
- mock(ActivityStarter.OnDismissAction::class.java),
- {},
- )
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNotNull()
-
- val dismissCallback = mock(IKeyguardDismissCallback::class.java)
- kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
underTest.onRemovedFromWindow()
-
- kosmos.fakeExecutor.runAllReady()
verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
- verify(dismissCallback).onDismissCancelled()
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNull()
}
@Test
fun onBackRequested() =
testScope.runTest {
- kosmos.primaryBouncerInteractor.setDismissAction(
- mock(ActivityStarter.OnDismissAction::class.java),
- {},
- )
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNotNull()
-
val dismissCallback = mock(IKeyguardDismissCallback::class.java)
kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
@@ -96,7 +76,6 @@
kosmos.fakeExecutor.runAllReady()
verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
verify(dismissCallback).onDismissCancelled()
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
similarity index 72%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index 7d57220..c7acd78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -17,14 +17,8 @@
package com.android.systemui.lifecycle
import android.view.View
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -32,8 +26,6 @@
import com.android.systemui.util.Assert
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -157,51 +149,9 @@
assertThat(viewModel.isActivated).isTrue()
}
-
- @Test
- fun hydratedStateOf() {
- val keepAliveMutable = mutableStateOf(true)
- val upstreamStateFlow = MutableStateFlow(true)
- val upstreamFlow = upstreamStateFlow.map { !it }
- composeRule.setContent {
- val keepAlive by keepAliveMutable
- if (keepAlive) {
- val viewModel = rememberViewModel {
- FakeSysUiViewModel(
- upstreamFlow = upstreamFlow,
- upstreamStateFlow = upstreamStateFlow,
- )
- }
-
- Column {
- Text(
- "upstreamStateFlow=${viewModel.stateBackedByStateFlow}",
- Modifier.testTag("upstreamStateFlow")
- )
- Text(
- "upstreamFlow=${viewModel.stateBackedByFlow}",
- Modifier.testTag("upstreamFlow")
- )
- }
- }
- }
-
- composeRule.waitForIdle()
- composeRule
- .onNode(hasTestTag("upstreamStateFlow"))
- .assertTextEquals("upstreamStateFlow=true")
- composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false")
-
- composeRule.runOnUiThread { upstreamStateFlow.value = false }
- composeRule.waitForIdle()
- composeRule
- .onNode(hasTestTag("upstreamStateFlow"))
- .assertTextEquals("upstreamStateFlow=false")
- composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true")
- }
}
-private class FakeViewModel : SysUiViewModel() {
+private class FakeViewModel : SysUiViewModel, ExclusiveActivatable() {
var isActivated = false
override suspend fun onActivated(): Nothing {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 3b541cd..99c5b7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -38,6 +38,9 @@
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Bundle
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper
@@ -48,10 +51,14 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.MediaDataRepository
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -69,6 +76,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.FakeSettings
@@ -79,6 +87,7 @@
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -126,6 +135,7 @@
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class MediaDataProcessorTest : SysuiTestCase() {
val kosmos = testKosmos()
@@ -146,7 +156,6 @@
@Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
@Mock lateinit var mediaDeviceManager: MediaDeviceManager
@Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
- @Mock lateinit var mediaDataFilter: MediaDataFilterImpl
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
@Mock lateinit var activityStarter: ActivityStarter
@@ -185,14 +194,14 @@
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
1
)
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
+ private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
private lateinit var staticMockSession: MockitoSession
@Before
fun setup() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
staticMockSession =
ExtendedMockito.mockitoSession()
.mockStatic<UriGrantsManager>(UriGrantsManager::class.java)
@@ -258,6 +267,7 @@
session = MediaSession(context, "MediaDataProcessorTestSession")
mediaNotification =
SbnBuilder().run {
+ setUser(UserHandle(USER_ID))
setPkg(PACKAGE_NAME)
modifyNotification(context).also {
it.setSmallIcon(android.R.drawable.ic_media_pause)
@@ -1798,6 +1808,85 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun postWithPlaybackActions_drawablesReused() =
+ kosmos.testScope.runTest {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val stateActions =
+ PlaybackState.ACTION_PAUSE or
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackState.ACTION_SKIP_TO_NEXT
+ val stateBuilder =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+
+ mediaDataProcessor.addInternalListener(mediaDataFilter)
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+ assertThat(secondSemanticActions.playOrPause?.icon)
+ .isEqualTo(firstSemanticActions.playOrPause?.icon)
+ assertThat(secondSemanticActions.playOrPause?.background)
+ .isEqualTo(firstSemanticActions.playOrPause?.background)
+ assertThat(secondSemanticActions.nextOrCustom?.icon)
+ .isEqualTo(firstSemanticActions.nextOrCustom?.icon)
+ assertThat(secondSemanticActions.prevOrCustom?.icon)
+ .isEqualTo(firstSemanticActions.prevOrCustom?.icon)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun postWithPlaybackActions_drawablesNotReused() =
+ kosmos.testScope.runTest {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val stateActions =
+ PlaybackState.ACTION_PAUSE or
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackState.ACTION_SKIP_TO_NEXT
+ val stateBuilder =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+
+ mediaDataProcessor.addInternalListener(mediaDataFilter)
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+ assertThat(secondSemanticActions.playOrPause?.icon)
+ .isNotEqualTo(firstSemanticActions.playOrPause?.icon)
+ assertThat(secondSemanticActions.playOrPause?.background)
+ .isNotEqualTo(firstSemanticActions.playOrPause?.background)
+ assertThat(secondSemanticActions.nextOrCustom?.icon)
+ .isNotEqualTo(firstSemanticActions.nextOrCustom?.icon)
+ assertThat(secondSemanticActions.prevOrCustom?.icon)
+ .isNotEqualTo(firstSemanticActions.prevOrCustom?.icon)
+ }
+
+ @Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 5142730..6a66c40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -254,22 +254,17 @@
// AND that token results in a null route
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ val data = loadMediaAndCaptureDeviceData()
+
// THEN the device should be disabled
- val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
}
@Test
fun deviceEventOnAddNotification() {
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the update is dispatched to the listener
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
assertThat(data.icon).isEqualTo(icon)
@@ -417,15 +412,40 @@
whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN it uses the route name (instead of device name)
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
}
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableReused() {
+ whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
+ whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute"))
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isEqualTo(firstData.icon)
+ }
+
+ @Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableNotReused() {
+ whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
+ whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute"))
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isNotEqualTo(firstData.icon)
+ }
+
@RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
@Test
fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
@@ -433,11 +453,8 @@
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the device is disabled and name is set to null
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isFalse()
assertThat(data.name).isNull()
}
@@ -449,23 +466,48 @@
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the device is disabled and name and icon are set to "OTHER DEVICE".
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isFalse()
assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device))
assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB)
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+ @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableReused() {
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+ context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isEqualTo(firstData.icon)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+ @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableNotReused() {
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+ context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isNotEqualTo(firstData.icon)
+ }
+
@RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
@Test
fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// AND MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -480,13 +522,12 @@
assertThat(data.enabled).isFalse()
assertThat(data.name).isNull()
}
+
@RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
@Test
fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_returnOtherDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// AND MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -507,9 +548,7 @@
@Test
fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// GIVEN that MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -529,9 +568,7 @@
@Test
fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// GIVEN that MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -563,12 +600,8 @@
whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
// Then the device name is the PhoneMediaDevice string
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
}
@@ -582,12 +615,8 @@
whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
whenever(routingSession.isSystemSession).thenReturn(true)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
// Then the device name is the selected route name
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
}
@@ -597,11 +626,8 @@
whenever(routingSession.name).thenReturn(null)
whenever(routingSession.isSystemSession).thenReturn(false)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the device is enabled and uses the current connected device name
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.name).isEqualTo(DEVICE_NAME)
assertThat(data.enabled).isTrue()
}
@@ -611,9 +637,7 @@
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
// GIVEN a controller with local playback type
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(mr2)
// WHEN onAudioInfoChanged fires with remote playback type
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -630,9 +654,7 @@
whenever(playbackInfo.getVolumeControlId()).thenReturn(null)
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
// GIVEN a controller with local playback type
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(mr2)
// WHEN onAudioInfoChanged fires with a volume control id change
whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id")
@@ -649,9 +671,7 @@
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
// GIVEN a controller with remote playback type
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(mr2)
// WHEN onAudioInfoChanged fires with remote playback type
val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
@@ -665,9 +685,7 @@
fun deviceIdChanged_informListener() {
// GIVEN a notification is added, with a particular device connected
whenever(device.id).thenReturn(DEVICE_ID)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
// and later the manager gets a new device ID
val deviceCallback = captureCallback()
@@ -694,9 +712,7 @@
// GIVEN a notification is added, with a particular device connected
whenever(device.id).thenReturn(DEVICE_ID)
whenever(device.name).thenReturn(DEVICE_NAME)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
// and later the manager gets a new device name
val deviceCallback = captureCallback()
@@ -725,12 +741,8 @@
whenever(device.name).thenReturn(DEVICE_NAME)
val firstIcon = mock(Drawable::class.java)
whenever(device.icon).thenReturn(firstIcon)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
- val dataCaptor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
- verify(listener).onMediaDeviceChanged(eq(KEY), any(), dataCaptor.capture())
+ loadMediaAndCaptureDeviceData()
// and later the manager gets a callback with only the icon changed
val deviceCallback = captureCallback()
@@ -772,11 +784,7 @@
setupBroadcastPackage(BROADCAST_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
@@ -791,11 +799,7 @@
setupBroadcastPackage(BROADCAST_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isTrue()
assertThat(data.enabled).isTrue()
assertThat(data.name)
@@ -811,11 +815,7 @@
setupBroadcastPackage(NORMAL_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isTrue()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(BROADCAST_APP_NAME)
@@ -829,11 +829,7 @@
setupLeAudioConfiguration(false)
broadcastCallback.onBroadcastStopped(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
}
@@ -846,11 +842,7 @@
setupBroadcastPackage(BROADCAST_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.enabled).isFalse()
assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
@@ -858,6 +850,82 @@
@Test
@DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
+ fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesReused() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+ assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_LEGACY_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesNotReused() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+ assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_LEGACY_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableReused() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(NORMAL_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+ assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableNotReused() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(NORMAL_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+ assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
@EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
val broadcastCallback = setupBroadcastCallback()
@@ -865,11 +933,7 @@
setupBroadcastPackage(NORMAL_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.enabled).isFalse()
assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
@@ -883,11 +947,7 @@
setupLeAudioConfiguration(false)
broadcastCallback.onBroadcastStopped(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.name?.equals(context.getString(R.string.audio_sharing_description)))
.isFalse()
@@ -903,13 +963,21 @@
val callback: BluetoothLeBroadcast.Callback =
object : BluetoothLeBroadcast.Callback {
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastStartFailed(reason: Int) {}
+
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastStopFailed(reason: Int) {}
+
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastMetadataChanged(
broadcastId: Int,
metadata: BluetoothLeBroadcastMetadata
@@ -941,4 +1009,12 @@
verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
return captor.getValue()
}
+
+ private fun loadMediaAndCaptureDeviceData(): MediaDeviceData {
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ return captureDeviceData(KEY)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index f8358c5..850916b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -186,7 +186,6 @@
sceneInteractor = kosmos.sceneInteractor,
)
verify(configurationController).addCallback(capture(configListener))
- verify(mediaDataManager).addListener(capture(listener))
verify(visualStabilityProvider)
.addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback))
@@ -405,8 +404,11 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
}
+ @DisableSceneContainer
@Test
fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
+ verify(mediaDataManager).addListener(capture(listener))
+
testPlayerOrdering()
// If smartspace is prioritized
@@ -439,8 +441,11 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
}
+ @DisableSceneContainer
@Test
fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
+ verify(mediaDataManager).addListener(capture(listener))
+
testPlayerOrdering()
// playing paused player
listener.value.onMediaDataLoaded(
@@ -547,8 +552,11 @@
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
+ @DisableSceneContainer
@Test
fun testMediaLoaded_ScrollToActivePlayer() {
+ verify(mediaDataManager).addListener(capture(listener))
+
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
null,
@@ -604,8 +612,11 @@
)
}
+ @DisableSceneContainer
@Test
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
+ verify(mediaDataManager).addListener(capture(listener))
+
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
@@ -647,8 +658,11 @@
assertEquals(playerIndex, 0)
}
+ @DisableSceneContainer
@Test
fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+ verify(mediaDataManager).addListener(capture(listener))
+
var result = false
mediaCarouselController.updateHostVisibility = { result = true }
@@ -658,8 +672,11 @@
assertEquals(true, result)
}
+ @DisableSceneContainer
@Test
fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+ verify(mediaDataManager).addListener(capture(listener))
+
var result = false
mediaCarouselController.updateHostVisibility = { result = true }
@@ -788,8 +805,11 @@
verify(pageIndicator, times(4)).setNumPages(any())
}
+ @DisableSceneContainer
@Test
fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
+ verify(mediaDataManager).addListener(capture(listener))
+
testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
// When an update to existing smartspace data is loaded
@@ -804,8 +824,11 @@
assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
}
+ @DisableSceneContainer
@Test
fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() {
+ verify(mediaDataManager).addListener(capture(listener))
+
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
// When inactive smartspace data is loaded
@@ -1023,11 +1046,13 @@
verify(panel).updateAnimatorDurationScale()
}
+ @DisableSceneContainer
@Test
fun swipeToDismiss_pausedAndResumeOff_userInitiated() {
+ verify(mediaDataManager).addListener(capture(listener))
+
// When resumption is disabled, paused media should be dismissed after being swiped away
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
-
val pausedMedia = DATA.copy(isPlaying = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
mediaCarouselController.onSwipeToDismiss()
@@ -1042,8 +1067,11 @@
verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
}
+ @DisableSceneContainer
@Test
fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() {
+ verify(mediaDataManager).addListener(capture(listener))
+
// When resumption is disabled, paused media should be dismissed after being swiped away
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
mediaCarouselController.updateHostVisibility = {}
@@ -1068,6 +1096,7 @@
* @param function called when a certain configuration change occurs.
*/
private fun testConfigurationChange(function: () -> Unit) {
+ verify(mediaDataManager).addListener(capture(listener))
mediaCarouselController.pageIndicator = pageIndicator
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 521aa5a..1260a65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -70,6 +70,7 @@
import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -84,7 +85,6 @@
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.monet.ColorScheme
@@ -141,6 +141,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@DisableSceneContainer
public class MediaControlPanelTest : SysuiTestCase() {
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@@ -233,9 +234,7 @@
@Mock private lateinit var recProgressBar1: SeekBar
@Mock private lateinit var recProgressBar2: SeekBar
@Mock private lateinit var recProgressBar3: SeekBar
- private var shouldShowBroadcastButton: Boolean = false
@Mock private lateinit var globalSettings: GlobalSettings
- @Mock private lateinit var mediaFlags: MediaFlags
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -254,7 +253,6 @@
.thenReturn(applicationInfo)
whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
context.setMockPackageManager(packageManager)
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
player =
object :
@@ -278,7 +276,6 @@
lockscreenUserManager,
broadcastDialogController,
globalSettings,
- mediaFlags,
) {
override fun loadAnimator(
animId: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 6c350cb..2370bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -40,7 +41,6 @@
import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -85,6 +85,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@DisableSceneContainer
class MediaHierarchyManagerTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -105,7 +106,6 @@
@Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var logger: MediaViewLogger
- @Mock private lateinit var mediaFlags: MediaFlags
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -139,7 +139,6 @@
shadeExpansion = MutableStateFlow(0f)
whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion)
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
mediaHierarchyManager =
MediaHierarchyManager(
context,
@@ -160,7 +159,6 @@
testScope.backgroundScope,
ResourcesSplitShadeStateController(),
logger,
- mediaFlags,
)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index 00b9a46..e765b6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -38,12 +38,12 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.CachingIconView
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
@@ -113,7 +113,6 @@
@Mock private lateinit var mediaTitleWidgetState: WidgetState
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
- @Mock private lateinit var mediaFlags: MediaFlags
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
@Mock private lateinit var globalSettings: GlobalSettings
@@ -140,7 +139,6 @@
logger,
seekBarViewModel,
mainExecutor,
- mediaFlags,
globalSettings,
) {
override fun loadAnimator(
@@ -374,10 +372,9 @@
verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
}
+ @EnableSceneContainer
@Test
fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
getEnabledChangeListener().onEnabledChanged(enabled = false)
@@ -386,10 +383,9 @@
.isEqualTo(ConstraintSet.INVISIBLE)
}
+ @EnableSceneContainer
@Test
fun attachPlayer_seekBarEnabled_seekBarVisible() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -397,10 +393,9 @@
.isEqualTo(ConstraintSet.VISIBLE)
}
+ @EnableSceneContainer
@Test
fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -413,10 +408,9 @@
.isEqualTo(ConstraintSet.INVISIBLE)
}
+ @EnableSceneContainer
@Test
fun attachPlayer_notScrubbing_scrubbingViewsGone() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.canShowScrubbingTime = true
getScrubbingChangeListener().onScrubbingChanged(true)
@@ -433,10 +427,9 @@
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.canShowScrubbingTime = false
getScrubbingChangeListener().onScrubbingChanged(true)
@@ -452,10 +445,9 @@
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true)
mediaViewController.setUpPrevButtonInfo(false)
@@ -474,10 +466,9 @@
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(false)
mediaViewController.setUpPrevButtonInfo(true)
@@ -496,10 +487,9 @@
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true)
mediaViewController.setUpPrevButtonInfo(true)
@@ -522,10 +512,9 @@
.isEqualTo(ConstraintSet.VISIBLE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index b67e111..5a5cdcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,7 +33,6 @@
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_GESTURE
import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
@@ -58,6 +57,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.controller.keyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
@@ -140,7 +140,8 @@
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
}
testableLooper = TestableLooper.get(this)
@@ -186,7 +187,8 @@
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
// First call succeeds.
@@ -214,7 +216,8 @@
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
@@ -237,7 +240,8 @@
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
// Only initView without attaching a view as we don't want the flows to start collecting
@@ -437,7 +441,7 @@
}
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
+ @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit() =
with(kosmos) {
testScope.runTest {
@@ -463,7 +467,6 @@
}
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
@EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit_fullSwipe() =
with(kosmos) {
@@ -484,7 +487,7 @@
}
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
+ @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit_rtl() =
with(kosmos) {
testScope.runTest {
@@ -509,7 +512,6 @@
}
}
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
@EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit_rtl_fullSwipe() =
with(kosmos) {
@@ -530,102 +532,6 @@
}
@Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
- @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ TOP_SWIPE_REGION_WIDTH,
- /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
- ),
- Rect(
- /* left= */ 0,
- /* top= */ 0,
- /* right= */ FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- }
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled_fullSwipe() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- /* left= */ 0,
- /* top= */ 0,
- /* right= */ FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- }
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
- @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ TOP_SWIPE_REGION_WIDTH,
- /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
- ),
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ 0,
- /* right= */ CONTAINER_WIDTH,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- }
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl_fullSwipe() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ 0,
- /* right= */ CONTAINER_WIDTH,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- )
- }
- }
-
- @Test
fun gestureExclusionZone_unsetWhenShadeOpen() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 50131cb..a0d231b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -32,6 +32,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.app.Flags;
import android.app.Notification;
import android.content.Context;
import android.content.ContextWrapper;
@@ -41,9 +42,13 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.notification.StatusBarNotification;
import android.view.ViewGroup;
@@ -191,6 +196,34 @@
}
@Test
+ @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+ public void setIcon_withPreloaded_usesPreloaded() {
+ Icon mockIcon = mock(Icon.class);
+ when(mockIcon.loadDrawableAsUser(any(), anyInt())).thenReturn(new ColorDrawable(1));
+ mStatusBarIcon.icon = mockIcon;
+ mStatusBarIcon.preloadedIcon = new ShapeDrawable();
+
+ mIconView.set(mStatusBarIcon);
+
+ assertThat(mIconView.getDrawable()).isNotNull();
+ assertThat(mIconView.getDrawable()).isInstanceOf(ShapeDrawable.class);
+ }
+
+ @Test
+ @DisableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+ public void setIcon_withPreloadedButFlagDisabled_ignoresPreloaded() {
+ Icon mockIcon = mock(Icon.class);
+ when(mockIcon.loadDrawableAsUser(any(), anyInt())).thenReturn(new ColorDrawable(1));
+ mStatusBarIcon.icon = mockIcon;
+ mStatusBarIcon.preloadedIcon = new ShapeDrawable();
+
+ mIconView.set(mStatusBarIcon);
+
+ assertThat(mIconView.getDrawable()).isNotNull();
+ assertThat(mIconView.getDrawable()).isInstanceOf(ColorDrawable.class);
+ }
+
+ @Test
public void testUpdateIconScale_constrainedDrawableSizeLessThanDpIconSize() {
int dpIconSize = 60;
int dpDrawingSize = 30;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index ad6aca1..3c583f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
@@ -45,8 +46,8 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -66,6 +67,7 @@
SensitiveNotificationProtectionController
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
+ @Mock private lateinit var row: ExpandableNotificationRow
@Before
fun setUp() {
@@ -74,6 +76,8 @@
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
entry = NotificationEntryBuilder().setSection(section).build()
+ entry.row = row
+ entry.setSensitive(false, false)
coordinator =
StackCoordinator(
groupExpansionManagerImpl,
@@ -189,4 +193,17 @@
.setNotifStats(NotifStats(1, false, false, true, false))
verifyZeroInteractions(stackController)
}
+
+ @Test
+ @EnableFlags(
+ FooterViewRefactor.FLAG_NAME
+ )
+ fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
+ entry.setSensitive(true, true)
+ whenever(section.bucket).thenReturn(BUCKET_ALERTING)
+ afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ verify(activeNotificationsInteractor)
+ .setNotifStats(NotifStats(1, hasNonClearableAlertingNotifs = true, false, false, false))
+ verifyZeroInteractions(stackController)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index dfee2ed..76dc65c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -17,17 +17,24 @@
package com.android.systemui.statusbar.phone
import android.app.AlarmManager
+import android.app.AutomaticZenRule
+import android.app.NotificationManager
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyResourcesManager
import android.content.SharedPreferences
+import android.net.Uri
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import android.service.notification.SystemZenRules
+import android.service.notification.ZenModeConfig
import android.telecom.TelecomManager
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -53,12 +60,13 @@
import com.android.systemui.statusbar.policy.SensorPrivacyController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.RingerModeTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.DateFormatUtil
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -83,7 +91,10 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -91,7 +102,11 @@
@SmallTest
class PhoneStatusBarPolicyTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+
companion object {
+ private const val ZEN_SLOT = "zen"
private const val ALARM_SLOT = "alarm"
private const val CAST_SLOT = "cast"
private const val SCREEN_RECORD_SLOT = "screen_record"
@@ -109,7 +124,6 @@
@Mock private lateinit var userInfoController: UserInfoController
@Mock private lateinit var rotationLockController: RotationLockController
@Mock private lateinit var dataSaverController: DataSaverController
- @Mock private lateinit var zenModeController: ZenModeController
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var locationController: LocationController
@@ -133,6 +147,7 @@
private val testScope = TestScope(UnconfinedTestDispatcher())
private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+ private val zenModeController = FakeZenModeController()
private lateinit var executor: FakeExecutor
private lateinit var statusBarPolicy: PhoneStatusBarPolicy
@@ -374,6 +389,102 @@
verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeInteractorActiveModeChanged_showsModeIcon() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId("bedtime")
+ .setName("Bedtime Mode")
+ .setType(AutomaticZenRule.TYPE_BEDTIME)
+ .setActive(true)
+ .setPackage("some.package")
+ .setIconResId(123)
+ .build(),
+ TestModeBuilder()
+ .setId("other")
+ .setName("Other Mode")
+ .setType(AutomaticZenRule.TYPE_OTHER)
+ .setActive(true)
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setIconResId(456)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
+ verify(iconController)
+ .setResourceIcon(
+ eq(ZEN_SLOT),
+ eq("some.package"),
+ eq(123),
+ eq(null),
+ eq("Bedtime Mode")
+ )
+
+ zenModeRepository.deactivateMode("bedtime")
+ runCurrent()
+
+ verify(iconController)
+ .setResourceIcon(eq(ZEN_SLOT), eq(null), eq(456), eq(null), eq("Other Mode"))
+
+ zenModeRepository.deactivateMode("other")
+ runCurrent()
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeControllerOnGlobalZenChanged_doesNotUpdateDndIcon() {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
+
+ verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
+ verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
+ verify(iconController, never()).setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeInteractorActiveModeChanged_withFlagDisabled_ignored() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeRepository.addMode(id = "Bedtime", active = true)
+ runCurrent()
+
+ verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
+ verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
+ verify(iconController, never())
+ .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeControllerOnGlobalZenChanged_withFlagDisabled_updatesDndIcon() {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
+ verify(iconController).setIcon(eq(ZEN_SLOT), anyInt(), eq("Priority only"))
+
+ zenModeController.setZen(Settings.Global.ZEN_MODE_OFF, null, null)
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
+ }
+
private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
return AlarmManager.AlarmClockInfo(10L, null)
}
@@ -412,6 +523,7 @@
privacyItemController,
privacyLogger,
fakeConnectedDisplayStateProvider,
+ kosmos.zenModeInteractor,
JavaAdapter(testScope.backgroundScope)
)
}
@@ -433,4 +545,51 @@
override val concurrentDisplaysInProgress: Flow<Boolean>
get() = TODO("Not yet implemented")
}
+
+ private class FakeZenModeController : ZenModeController {
+
+ private val callbacks = mutableListOf<ZenModeController.Callback>()
+ private var zen = Settings.Global.ZEN_MODE_OFF
+ private var consolidatedPolicy = NotificationManager.Policy(0, 0, 0)
+
+ override fun addCallback(listener: ZenModeController.Callback) {
+ callbacks.add(listener)
+ }
+
+ override fun removeCallback(listener: ZenModeController.Callback) {
+ callbacks.remove(listener)
+ }
+
+ override fun setZen(zen: Int, conditionId: Uri?, reason: String?) {
+ this.zen = zen
+ callbacks.forEach { it.onZenChanged(zen) }
+ }
+
+ override fun getZen(): Int = zen
+
+ override fun getManualRule(): ZenModeConfig.ZenRule = throw NotImplementedError()
+
+ override fun getConfig(): ZenModeConfig = throw NotImplementedError()
+
+ fun setConsolidatedPolicy(policy: NotificationManager.Policy) {
+ this.consolidatedPolicy = policy
+ callbacks.forEach { it.onConsolidatedPolicyChanged(consolidatedPolicy) }
+ }
+
+ override fun getConsolidatedPolicy(): NotificationManager.Policy = consolidatedPolicy
+
+ override fun getNextAlarm() = throw NotImplementedError()
+
+ override fun isZenAvailable() = throw NotImplementedError()
+
+ override fun getEffectsSuppressor() = throw NotImplementedError()
+
+ override fun isCountdownConditionSupported() = throw NotImplementedError()
+
+ override fun getCurrentUser() = throw NotImplementedError()
+
+ override fun isVolumeRestricted() = throw NotImplementedError()
+
+ override fun areNotificationsHiddenInShade() = throw NotImplementedError()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 9b61105..b75ac2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -108,7 +108,9 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.time.FakeSystemClock;
import com.google.common.truth.Truth;
@@ -175,6 +177,7 @@
mBouncerExpansionCallback;
private FakeKeyguardStateController mKeyguardStateController =
spy(new FakeKeyguardStateController());
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Mock
private ViewRootImpl mViewRootImpl;
@@ -238,6 +241,7 @@
mock(JavaAdapter.class),
() -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
+ mExecutor,
() -> mDeviceEntryInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
@@ -760,6 +764,7 @@
mock(JavaAdapter.class),
() -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
+ mExecutor,
() -> mDeviceEntryInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
@@ -1084,6 +1089,9 @@
.thenReturn(KeyguardState.LOCKSCREEN);
reset(mCentralSurfaces);
+ // Advance past reattempts
+ mStatusBarKeyguardViewManager.setAttemptsToShowBouncer(10);
+
mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
verify(mPrimaryBouncerInteractor).show(true);
verify(mCentralSurfaces).showKeyguard();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
index 19abbd5..26a57e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.phone.ui
+import android.graphics.drawable.ColorDrawable
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.StatusBarIcon
@@ -406,6 +408,33 @@
.isInstanceOf(StatusBarIconHolder.BindableIconHolder::class.java)
}
+ @Test
+ fun setIcon_setsIconInHolder() {
+ underTest.setIcon("slot", 123, "description")
+
+ val iconHolder = iconList.getIconHolder("slot", 0)
+ assertThat(iconHolder).isNotNull()
+ assertThat(iconHolder?.icon?.pkg).isEqualTo(mContext.packageName)
+ assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123)
+ assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo(mContext.packageName)
+ assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description")
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun setResourceIcon_setsIconAndPreloadedIconInHolder() {
+ val drawable = ColorDrawable(1)
+ underTest.setResourceIcon("slot", "some.package", 123, drawable, "description")
+
+ val iconHolder = iconList.getIconHolder("slot", 0)
+ assertThat(iconHolder).isNotNull()
+ assertThat(iconHolder?.icon?.pkg).isEqualTo("some.package")
+ assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123)
+ assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo("some.package")
+ assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description")
+ assertThat(iconHolder?.icon?.preloadedIcon).isEqualTo(drawable)
+ }
+
private fun createExternalIcon(): StatusBarIcon {
return StatusBarIcon(
"external.package",
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
new file mode 100644
index 0000000..5ad973a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.systemui.haptics.msdl
+
+import com.google.android.msdl.data.model.FeedbackLevel
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
+
+class FakeMSDLPlayer : MSDLPlayer {
+ var currentFeedbackLevel = FeedbackLevel.DEFAULT
+ var latestTokenPlayed: MSDLToken? = null
+ private set
+
+ var latestPropertiesPlayed: InteractionProperties? = null
+ private set
+
+ override fun getSystemFeedbackLevel(): FeedbackLevel = currentFeedbackLevel
+
+ override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
+ latestTokenPlayed = token
+ latestPropertiesPlayed = properties
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
new file mode 100644
index 0000000..f5a05b4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.systemui.haptics.msdl
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.msdlPlayer by Kosmos.Fixture { FakeMSDLPlayer() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
index f1d87fe..2958315 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -33,6 +32,5 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
dismissCallbackRegistry = dismissCallbackRegistry,
alternateBouncerInteractor = { alternateBouncerInteractor },
- primaryBouncerInteractor = primaryBouncerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
index 4c05939..e66a2be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
@@ -21,7 +21,7 @@
class FakeActivatable(
private val onActivation: () -> Unit = {},
private val onDeactivation: () -> Unit = {},
-) : BaseActivatable() {
+) : ExclusiveActivatable() {
var activationCount = 0
var cancellationCount = 0
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
index 90cd8c7..1652462 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.lifecycle
import androidx.compose.runtime.getValue
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,19 +28,21 @@
private val onDeactivation: () -> Unit = {},
private val upstreamFlow: Flow<Boolean> = flowOf(true),
private val upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(),
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
var activationCount = 0
var cancellationCount = 0
- val stateBackedByFlow: Boolean by hydratedStateOf(initialValue = true, source = upstreamFlow)
- val stateBackedByStateFlow: Boolean by hydratedStateOf(source = upstreamStateFlow)
+ private val hydrator = Hydrator()
+ val stateBackedByFlow: Boolean by
+ hydrator.hydratedStateOf(initialValue = true, source = upstreamFlow)
+ val stateBackedByStateFlow: Boolean by hydrator.hydratedStateOf(source = upstreamStateFlow)
override suspend fun onActivated(): Nothing {
activationCount++
onActivation()
try {
- awaitCancellation()
+ hydrator.activate()
} finally {
cancellationCount++
onDeactivation()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
index 1473184..61d5f1e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
@@ -26,7 +26,7 @@
import com.android.systemui.media.controls.util.mediaUiEventLogger
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.notificationLockscreenUserManager
-import com.android.systemui.util.time.systemClock
+import com.android.systemui.util.time.fakeSystemClock
import com.android.systemui.util.wakelock.WakeLockFake
val Kosmos.mediaDataFilter by
@@ -42,7 +42,7 @@
),
lockscreenUserManager = notificationLockscreenUserManager,
executor = fakeExecutor,
- systemClock = systemClock,
+ systemClock = fakeSystemClock,
logger = mediaUiEventLogger,
mediaFlags = mediaFlags,
mediaFilterRepository = mediaFilterRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
index 64e3526..78358f5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
@@ -19,6 +19,8 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
@@ -26,7 +28,7 @@
class FakeScene(
override val key: SceneKey,
-) : Scene {
+) : ExclusiveActivatable(), Scene {
var isDestinationScenesBeingCollected = false
private val destinationScenesChannel = Channel<Map<UserAction, UserActionResult>>()
@@ -37,6 +39,10 @@
.onStart { isDestinationScenesBeingCollected = true }
.onCompletion { isDestinationScenesBeingCollected = false }
+ override suspend fun onActivated(): Nothing {
+ awaitCancellation()
+ }
+
suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) {
destinationScenesChannel.send(value)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
new file mode 100644
index 0000000..7e51135
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
+
+fun Kosmos.getEnRouteViewModel(repository: NotificationRowRepository) =
+ EnRouteViewModel(
+ dumpManager = dumpManager,
+ rowInteractor = getNotificationRowInteractor(repository),
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/ReferenceTestUtils.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/leak/ReferenceTestUtils.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/leak/ReferenceTestUtils.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/leak/ReferenceTestUtils.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index a8328e4..2dbac67 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -14,8 +14,11 @@
package com.android.systemui.utils.leaks;
+import android.graphics.drawable.Drawable;
import android.testing.LeakCheck;
+import androidx.annotation.Nullable;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.ui.IconManager;
@@ -60,6 +63,11 @@
}
@Override
+ public void setResourceIcon(String slot, @Nullable String resPackage, int iconResId,
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription) {
+ }
+
+ @Override
public void setNewWifiIcon() {
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 09068d5..26b0f61 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -2605,11 +2605,7 @@
ret.size.height = imageReaderOutputConfig.getSize().getHeight();
ret.imageFormat = imageReaderOutputConfig.getImageFormat();
ret.capacity = imageReaderOutputConfig.getMaxImages();
- if (EFV_SUPPORTED) {
- ret.usage = imageReaderOutputConfig.getUsage();
- } else {
- ret.usage = 0;
- }
+ ret.usage = imageReaderOutputConfig.getUsage();
} else if (output instanceof MultiResolutionImageReaderOutputConfigImpl) {
MultiResolutionImageReaderOutputConfigImpl multiResReaderConfig =
(MultiResolutionImageReaderOutputConfigImpl) output;
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 58cd2e4..be4cd76 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -211,6 +211,7 @@
libs: [
"junit",
"flag-junit",
+ "framework-annotations-lib",
],
visibility: ["//visibility:public"],
}
@@ -331,6 +332,7 @@
name: "ravenwood-runtime",
data: [
"framework-res",
+ "ravenwood-empty-res",
],
libs: [
"100-framework-minus-apex.ravenwood",
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 691d06e..7e2ee3e 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -52,10 +52,6 @@
"host": true
},
{
- "name": "RavenwoodCoreTest",
- "host": true
- },
- {
"name": "RavenwoodResApkTest",
"host": true
},
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/bivalenttest/Android.bp
index 06cf08e6..e897735 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/bivalenttest/Android.bp
@@ -39,6 +39,9 @@
"androidx.test.ext.junit",
"androidx.test.rules",
+ "junit-params",
+ "platform-parametric-runner-lib",
+
// To make sure it won't cause VerifyError (b/324063814)
"platformprotosnano",
],
@@ -65,6 +68,9 @@
"androidx.test.ext.junit",
"androidx.test.rules",
+ "junit-params",
+ "platform-parametric-runner-lib",
+
"ravenwood-junit",
],
jni_libs: [
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
new file mode 100644
index 0000000..8dadd39
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.fail;
+
+import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
+
+import android.util.Log;
+
+import java.lang.StackWalker.StackFrame;
+import java.util.HashMap;
+
+/**
+ * Used to keep track of and count the number of calls.
+ */
+public class CallTracker {
+ public static final String TAG = "CallTracker";
+
+ private final HashMap<String, Integer> mNumCalled = new HashMap<>();
+
+ /**
+ * Call it when a method is called. It increments the count for the calling method.
+ */
+ public void incrementMethodCallCount() {
+ var methodName = getCallingMethodName(1);
+
+ Log.i(TAG, "Method called: " + methodName);
+
+ mNumCalled.put(methodName, getNumCalled(methodName) + 1);
+ }
+
+ /**
+ * Return the number of calls of a method.
+ */
+ public int getNumCalled(String methodName) {
+ return mNumCalled.getOrDefault(methodName, 0);
+ }
+
+ /**
+ * Return the current method name. (with the class name.)
+ */
+ private static String getCallingMethodName(int frameOffset) {
+ var walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
+ var caller = walker.walk(frames ->
+ frames.skip(1 + frameOffset).findFirst().map(StackFrame::getMethodName)
+ );
+ return caller.get();
+ }
+
+ /**
+ * Check the number of calls stored in {@link #mNumCalled}.
+ */
+ protected void assertCalls(Object... methodNameAndCountPairs) {
+ // Create a local copy
+ HashMap<String, Integer> counts = new HashMap<>(mNumCalled);
+ for (int i = 0; i < methodNameAndCountPairs.length - 1; i += 2) {
+ String methodName = (String) methodNameAndCountPairs[i];
+ int expectedCount = (Integer) methodNameAndCountPairs[i + 1];
+
+ if (getNumCalled(methodName) != expectedCount) {
+ fail(String.format("Method %s: expected call count=%d, actual=%d",
+ methodName, expectedCount, getNumCalled(methodName)));
+ }
+ counts.remove(methodName);
+ }
+ // All other entries are expected to be 0.
+ var sb = new StringBuilder();
+ for (var e : counts.entrySet()) {
+ if (e.getValue() == 0) {
+ continue;
+ }
+ sb.append(String.format("Method %s: expected call count=0, actual=%d",
+ e.getKey(), e.getValue()));
+ }
+ if (sb.length() > 0) {
+ fail(sb.toString());
+ }
+ }
+
+ /**
+ * Same as {@link #assertCalls(Object...)} but it kills the process if it fails.
+ * Only use in @AfterClass.
+ */
+ protected void assertCallsOrDie(Object... methodNameAndCountPairs) {
+ try {
+ assertCalls(methodNameAndCountPairs);
+ } catch (Throwable th) {
+ // TODO: I don't think it's by spec, but the exception here would be ignored both on
+ // ravenwood and on the device side. Look into it.
+ Log.e(TAG, "*** Failure detected in @AfterClass! ***", th);
+ Log.e(TAG, "JUnit seems to ignore exceptions from @AfterClass, so killing self.");
+ System.exit(7);
+ }
+ }
+
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
new file mode 100644
index 0000000..d7c2c6c
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.assertFalse;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure RavenwoodAwareTestRunnerTest properly delegates to the original runner,
+ * and also run the special annotated methods.
+ */
+@RunWith(JUnitParamsRunner.class)
+public class RavenwoodAwareTestRunnerTest {
+ public static final String TAG = "RavenwoodAwareTestRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ private static int getExpectedRavenwoodRunnerInitializingNumCalls() {
+ return RavenwoodRule.isOnRavenwood() ? 1 : 0;
+ }
+
+ @RavenwoodTestRunnerInitializing
+ public static void ravenwoodRunnerInitializing() {
+ // No other calls should have been made.
+ sCallTracker.assertCalls();
+
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ sCallTracker.assertCalls(
+ "ravenwoodRunnerInitializing",
+ getExpectedRavenwoodRunnerInitializingNumCalls()
+ );
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void test1() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ @Parameters({"foo", "bar"})
+ public void testWithParams(String arg) {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ @DisabledOnRavenwood
+ public void testDeviceOnly() {
+ assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "ravenwoodRunnerInitializing",
+ getExpectedRavenwoodRunnerInitializingNumCalls(),
+ "beforeClass", 1,
+ "test1", 1,
+ "testWithParams", 2
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
new file mode 100644
index 0000000..0f8be0e
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood
+public class RavenwoodImplicitClassRuleDeviceOnlyTest {
+ public static final String TAG = "RavenwoodImplicitClassRuleDeviceOnlyTest";
+
+ @BeforeClass
+ public static void beforeClass() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+
+ @Test
+ public void testDeviceOnly() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ if (RavenwoodRule.isOnRavenwood()) {
+ Log.e(TAG, "Even @AfterClass shouldn't be executed!");
+ System.exit(1);
+ }
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
new file mode 100644
index 0000000..7ef40dc
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assume;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+
+/**
+ * Make sure ravenizer will inject implicit rules and rewrite the existing rules' orders.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodImplicitRuleOrderRewriteTest {
+
+ private static final TestRule sEmptyRule = (statement, description) -> statement;
+
+ // We have two sets of 9 rules below, for class rules and instance rules.
+ // - Ravenizer will inject 2 more rules of each kind.
+ // - Ravenizer will adjust their order, so even though we'll add two sets of class and instance
+ // rules with a MIN / MAX order, there will still be no duplicate in the order.
+
+ private static final int EXPECTED_RULE_COUNT = 9 + 2;
+
+ @ClassRule(order = Integer.MIN_VALUE)
+ public static final TestRule sRule01 = sEmptyRule;
+
+ @ClassRule(order = Integer.MIN_VALUE + 1)
+ public static final TestRule sRule02 = sEmptyRule;
+
+ @ClassRule(order = -10)
+ public static final TestRule sRule03 = sEmptyRule;
+
+ @ClassRule(order = -1)
+ public static final TestRule sRule04 = sEmptyRule;
+
+ @ClassRule(order = 0)
+ public static final TestRule sRule05 = sEmptyRule;
+
+ @ClassRule(order = 1)
+ public static final TestRule sRule06 = sEmptyRule;
+
+ @ClassRule(order = 10)
+ public static final TestRule sRule07 = sEmptyRule;
+
+ @ClassRule(order = Integer.MAX_VALUE - 1)
+ public static final TestRule sRule08 = sEmptyRule;
+
+ @ClassRule(order = Integer.MAX_VALUE)
+ public static final TestRule sRule09 = sEmptyRule;
+
+ @Rule(order = Integer.MIN_VALUE)
+ public final TestRule mRule01 = sEmptyRule;
+
+ @Rule(order = Integer.MIN_VALUE + 1)
+ public final TestRule mRule02 = sEmptyRule;
+
+ @Rule(order = -10)
+ public final TestRule mRule03 = sEmptyRule;
+
+ @Rule(order = -1)
+ public final TestRule mRule04 = sEmptyRule;
+
+ @Rule(order = 0)
+ public final TestRule mRule05 = sEmptyRule;
+
+ @Rule(order = 1)
+ public final TestRule mRule06 = sEmptyRule;
+
+ @Rule(order = 10)
+ public final TestRule mRule07 = sEmptyRule;
+
+ @Rule(order = Integer.MAX_VALUE - 1)
+ public final TestRule mRule08 = sEmptyRule;
+
+ @Rule(order = Integer.MAX_VALUE)
+ public final TestRule mRule09 = sEmptyRule;
+
+ private void checkRules(boolean classRule) {
+ final var anotClass = classRule ? ClassRule.class : Rule.class;
+
+ final HashMap<Integer, Integer> ordersUsed = new HashMap<>();
+
+ for (var field : this.getClass().getDeclaredFields()) {
+ if (!field.isAnnotationPresent(anotClass)) {
+ continue;
+ }
+ final var anot = field.getAnnotation(anotClass);
+ final int order = classRule ? ((ClassRule) anot).order() : ((Rule) anot).order();
+
+ if (ordersUsed.containsKey(order)) {
+ fail("Detected duplicate order=" + order);
+ }
+ ordersUsed.put(order, 1);
+ }
+ assertEquals(EXPECTED_RULE_COUNT, ordersUsed.size());
+ }
+
+ @Test
+ public void testClassRules() {
+ Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+
+ checkRules(true);
+ }
+
+ @Test
+ public void testInstanceRules() {
+ Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+
+ checkRules(false);
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
new file mode 100644
index 0000000..ae596b1
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test to make sure when a test class inherits another test class, the base class's
+ * implicit rules are shadowed and won't be executed.
+ *
+ * ... But for now, we don't have a way to programmatically check it, so for now we need to
+ * check the log file manually.
+ *
+ * TODO: Implement the test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodImplicitRuleShadowingTest extends RavenwoodImplicitRuleShadowingTestBase {
+ @Test
+ public void testOkInSubClass() {
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
new file mode 100644
index 0000000..1ca97af
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * A test class that's just inherited by RavenwoodImplicitRuleShadowingTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public abstract class RavenwoodImplicitRuleShadowingTestBase {
+ @Test
+ public void testOkInBaseClass() {
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
new file mode 100644
index 0000000..c042eb0
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure ravenwood's test runner works with {@link AndroidJUnit4}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRunnerWithAndroidXRunnerTest {
+ public static final String TAG = "RavenwoodRunnerWithAndroidXRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ @BeforeClass
+ public static void beforeClass() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Before
+ public void beforeTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @After
+ public void afterTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void test1() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void test2() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "beforeClass", 1,
+ "beforeTest", 2,
+ "afterTest", 2,
+ "test1", 1,
+ "test2", 1
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
new file mode 100644
index 0000000..2feb5ba
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure ravenwood's test runner works with {@link AndroidJUnit4}.
+ */
+@RunWith(JUnitParamsRunner.class)
+public class RavenwoodRunnerWithJUnitParamsRunnerTest {
+ public static final String TAG = "RavenwoodRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ @BeforeClass
+ public static void beforeClass() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Before
+ public void beforeTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @After
+ public void afterTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void testWithNoParams() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ @Parameters({"foo", "bar"})
+ public void testWithParams(String arg) {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "beforeClass", 1,
+ "beforeTest", 3,
+ "afterTest", 3,
+ "testWithNoParams", 1,
+ "testWithParams", 2
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
new file mode 100644
index 0000000..7e3bc0f
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Make sure ravenwood's test runner works with {@link ParameterizedAndroidJunit4}.
+ */
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodRunnerWithParameterizedAndroidJunit4Test {
+ public static final String TAG = "RavenwoodRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ private final String mParam;
+
+ private static int sNumInsantiation = 0;
+
+ public RavenwoodRunnerWithParameterizedAndroidJunit4Test(String param) {
+ mParam = param;
+ sNumInsantiation++;
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ // It seems like ParameterizedAndroidJunit4 calls the @BeforeTest / @AfterTest methods
+ // one time too many.
+ // With two parameters, this method should be called only twice, but it's actually
+ // called three times.
+ // So let's not check the number fo beforeClass calls.
+ }
+
+ @Before
+ public void beforeTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @After
+ public void afterTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Parameters
+ public static List<String> getParams() {
+ var params = new ArrayList<String>();
+ params.add("foo");
+ params.add("bar");
+ return params;
+ }
+
+ @Test
+ public void testWithParams() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "beforeTest", sNumInsantiation,
+ "afterTest", sNumInsantiation,
+ "testWithParams", sNumInsantiation
+ );
+ }
+}
diff --git a/ravenwood/coretest/Android.bp b/ravenwood/coretest/Android.bp
deleted file mode 100644
index a78c5c1..0000000
--- a/ravenwood/coretest/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-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_ravenwood_test {
- name: "RavenwoodCoreTest",
-
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.test.ext.junit",
- "androidx.test.rules",
- ],
- srcs: [
- "test/**/*.java",
- ],
- sdk_version: "test_current",
- auto_gen_config: true,
-}
diff --git a/ravenwood/coretest/README.md b/ravenwood/coretest/README.md
deleted file mode 100644
index b60bfbf..0000000
--- a/ravenwood/coretest/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Ravenwood core test
-
-This test contains (non-bivalent) tests for Ravenwood itself -- e.g. tests for the ravenwood rules.
\ No newline at end of file
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
deleted file mode 100644
index f1e33cb..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 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.ravenwoodtest.coretest;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4; // Intentionally use the deprecated one.
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * Test for the test runner validator in RavenwoodRule.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestRunnerValidationTest {
- // Note the following rules don't have a @Rule, because they need to be applied in a specific
- // order. So we use a RuleChain instead.
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestRunnerValidationTest() {
- Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled());
- // Because RavenwoodRule will throw this error before executing the test method,
- // we can't do it in the test method itself.
- // So instead, we initialize it here.
- mThrown.expectMessage("Switch to androidx.test.ext.junit.runners.AndroidJUnit4");
- }
-
- @Test
- public void testValidateTestRunner() {
- }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
deleted file mode 100644
index db95fad..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2024 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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail01_Test {
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestMethodValidation_Fail01_Test() {
- mThrown.expectMessage("Method setUp() doesn't have @Before");
- }
-
- @SuppressWarnings("JUnit4SetUpNotRun")
- public void setUp() {
- }
-
- @Test
- public void testEmpty() {
- }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
deleted file mode 100644
index ddc66c7..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2024 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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail02_Test {
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestMethodValidation_Fail02_Test() {
- mThrown.expectMessage("Method tearDown() doesn't have @After");
- }
-
- @SuppressWarnings("JUnit4TearDownNotRun")
- public void tearDown() {
- }
-
- @Test
- public void testEmpty() {
- }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
deleted file mode 100644
index ec8e907..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2024 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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail03_Test {
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestMethodValidation_Fail03_Test() {
- mThrown.expectMessage("Method testFoo() doesn't have @Test");
- }
-
- @SuppressWarnings("JUnit4TestNotRun")
- public void testFoo() {
- }
-
- @Test
- public void testEmpty() {
- }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java
deleted file mode 100644
index d952d07..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_OkTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Before
- public void setUp() {
- }
-
- @Before
- public void testSetUp() {
- }
-
- @After
- public void tearDown() {
- }
-
- @After
- public void testTearDown() {
- }
-
- @Test
- public void testEmpty() {
- }
-}
diff --git a/ravenwood/empty-res/Android.bp b/ravenwood/empty-res/Android.bp
new file mode 100644
index 0000000..3af7690
--- /dev/null
+++ b/ravenwood/empty-res/Android.bp
@@ -0,0 +1,4 @@
+android_app {
+ name: "ravenwood-empty-res",
+ sdk_version: "current",
+}
diff --git a/ravenwood/empty-res/AndroidManifest.xml b/ravenwood/empty-res/AndroidManifest.xml
new file mode 100644
index 0000000..f73460b
--- /dev/null
+++ b/ravenwood/empty-res/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.ravenwood.emptyres">
+</manifest>
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
new file mode 100644
index 0000000..03600ad
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
+
+import android.os.Bundle;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Provide hook points created by {@link RavenwoodAwareTestRunner}.
+ */
+public class RavenwoodAwareTestRunnerHook {
+ private static final String TAG = "RavenwoodAwareTestRunnerHook";
+
+ private RavenwoodAwareTestRunnerHook() {
+ }
+
+ private static void log(String message) {
+ RavenwoodCommonUtils.log(TAG, message);
+ }
+
+ public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+ log("onRunnerStart: testClass=" + testClass + " runner=" + runner);
+
+ // TODO: Move the initialization code to a better place.
+
+ // This will let AndroidJUnit4 use the original runner.
+ System.setProperty("android.junit.runner",
+ "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
+ System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
+
+ // This is needed to make AndroidJUnit4ClassRunner happy.
+ InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+ }
+
+ public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order) {
+ log("onBefore: description=" + description + ", " + scope + ", " + order);
+
+ // Class-level annotations are checked by the runner already, so we only check
+ // method-level annotations here.
+ if (scope == Scope.Instance && order == Order.First) {
+ if (!RavenwoodRule.shouldEnableOnRavenwood(description)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static void onAfter(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order, Throwable th) {
+ log("onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 3ea4cb7..7b4c173 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,9 +16,9 @@
package android.platform.test.ravenwood;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -39,24 +39,12 @@
import com.android.internal.os.RuntimeInit;
import com.android.server.LocalServices;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
import org.junit.runner.Description;
-import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executors;
@@ -109,10 +97,6 @@
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
-// android.os.SystemProperties.init$ravenwood(
-// rule.mSystemProperties.getValues(),
-// rule.mSystemProperties.getKeyReadablePredicate(),
-// rule.mSystemProperties.getKeyWritablePredicate());
setSystemProperties(rule.mSystemProperties);
ServiceManager.init$ravenwood();
@@ -131,11 +115,12 @@
// TODO This should be integrated into LoadedApk
final Supplier<Resources> resourcesSupplier = () -> {
- final var resApkFile = new File(RAVENWOOD_RESOURCE_APK).getAbsoluteFile();
+ var resApkFile = new File(RAVENWOOD_RESOURCE_APK);
+ if (!resApkFile.isFile()) {
+ resApkFile = new File(RAVENWOOD_EMPTY_RESOURCES_APK);
+ }
assertTrue(resApkFile.isFile());
-
- final var res = resApkFile.getAbsolutePath();
-
+ final String res = resApkFile.getAbsolutePath();
final var emptyPaths = new String[0];
ResourcesManager.getInstance().initializeApplicationPaths(res, emptyPaths);
@@ -243,102 +228,7 @@
public static void validate(Statement base, Description description,
boolean enableOptionalValidation) {
- validateTestRunner(base, description, enableOptionalValidation);
- validateTestAnnotations(base, description, enableOptionalValidation);
- }
-
- private static void validateTestRunner(Statement base, Description description,
- boolean shouldFail) {
- final var testClass = description.getTestClass();
- final var runWith = testClass.getAnnotation(RunWith.class);
- if (runWith == null) {
- return;
- }
-
- // Due to build dependencies, we can't directly refer to androidx classes here,
- // so just check the class name instead.
- if (runWith.value().getCanonicalName().equals("androidx.test.runner.AndroidJUnit4")) {
- var message = "Test " + testClass.getCanonicalName() + " uses deprecated"
- + " test runner androidx.test.runner.AndroidJUnit4."
- + " Switch to androidx.test.ext.junit.runners.AndroidJUnit4.";
- if (shouldFail) {
- Assert.fail(message);
- } else {
- System.err.println("Warning: " + message);
- }
- }
- }
-
- /**
- * @return if a method has any of annotations.
- */
- private static boolean hasAnyAnnotations(Method m, Class<? extends Annotation>... annotations) {
- for (var anno : annotations) {
- if (m.getAnnotation(anno) != null) {
- return true;
- }
- }
- return false;
- }
-
- private static void validateTestAnnotations(Statement base, Description description,
- boolean enableOptionalValidation) {
- final var testClass = description.getTestClass();
-
- final var message = new StringBuilder();
-
- boolean hasErrors = false;
- for (Method m : collectMethods(testClass)) {
- if (Modifier.isPublic(m.getModifiers()) && m.getName().startsWith("test")) {
- if (!hasAnyAnnotations(m, Test.class, Before.class, After.class,
- BeforeClass.class, AfterClass.class)) {
- message.append("\nMethod " + m.getName() + "() doesn't have @Test");
- hasErrors = true;
- }
- }
- if ("setUp".equals(m.getName())) {
- if (!hasAnyAnnotations(m, Before.class)) {
- message.append("\nMethod " + m.getName() + "() doesn't have @Before");
- hasErrors = true;
- }
- if (!Modifier.isPublic(m.getModifiers())) {
- message.append("\nMethod " + m.getName() + "() must be public");
- hasErrors = true;
- }
- }
- if ("tearDown".equals(m.getName())) {
- if (!hasAnyAnnotations(m, After.class)) {
- message.append("\nMethod " + m.getName() + "() doesn't have @After");
- hasErrors = true;
- }
- if (!Modifier.isPublic(m.getModifiers())) {
- message.append("\nMethod " + m.getName() + "() must be public");
- hasErrors = true;
- }
- }
- }
- assertFalse("Problem(s) detected in class " + testClass.getCanonicalName() + ":"
- + message, hasErrors);
- }
-
- /**
- * Collect all (public or private or any) methods in a class, including inherited methods.
- */
- private static List<Method> collectMethods(Class<?> clazz) {
- var ret = new ArrayList<Method>();
- collectMethods(clazz, ret);
- return ret;
- }
-
- private static void collectMethods(Class<?> clazz, List<Method> result) {
- // Class.getMethods() only return public methods, so we need to use getDeclaredMethods()
- // instead, and recurse.
- for (var m : clazz.getDeclaredMethods()) {
- result.add(m);
- }
- if (clazz.getSuperclass() != null) {
- collectMethods(clazz.getSuperclass(), result);
- }
+ // Nothing to check, for now.
}
/**
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
new file mode 100644
index 0000000..a4fa41a
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood;
+
+import static android.platform.test.ravenwood.RavenwoodRule.shouldRunCassOnRavenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+import com.android.ravenwood.common.SneakyThrow;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.InvalidOrderingException;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Orderable;
+import org.junit.runner.manipulation.Orderer;
+import org.junit.runner.manipulation.Sortable;
+import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A test runner used for Ravenwood.
+ *
+ * TODO: Handle ENABLE_PROBE_IGNORED
+ *
+ * It will delegate to another runner specified with {@link InnerRunner}
+ * (default = {@link BlockJUnit4ClassRunner}) with the following features.
+ * - Add a {@link RavenwoodAwareTestRunnerHook#onRunnerInitializing} hook, which is called before
+ * the inner runner gets a chance to run. This can be used to initialize stuff used by the
+ * inner runner.
+ * - Add hook points, which are handed by RavenwoodAwareTestRunnerHook, with help from
+ * the four test rules such as {@link #sImplicitClassMinRule}, which are also injected by
+ * the ravenizer tool.
+ *
+ * We use this runner to:
+ * - Initialize the bare minimum environmnet just to be enough to make the actual test runners
+ * happy.
+ * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
+ *
+ * This class is built such that it can also be used on a real device, but in that case
+ * it will basically just delegate to the inner wrapper, and won't do anything special.
+ * (no hooks, etc.)
+ */
+public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
+ private static final String TAG = "RavenwoodAwareTestRunner";
+
+ @Inherited
+ @Target({TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface InnerRunner {
+ Class<? extends Runner> value();
+ }
+
+ /**
+ * An annotation similar to JUnit's BeforeClass, but this gets executed before
+ * the inner runner is instantiated, and only on Ravenwood.
+ * It can be used to initialize what's needed by the inner runner.
+ */
+ @Target({METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface RavenwoodTestRunnerInitializing {
+ }
+
+ /** Scope of a hook. */
+ public enum Scope {
+ Runner,
+ Class,
+ Instance,
+ }
+
+ /** Order of a hook. */
+ public enum Order {
+ First,
+ Last,
+ }
+
+ // The following four rule instances will be injected to tests by the Ravenizer tool.
+
+ public static final TestRule sImplicitClassMinRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Class, Order.First);
+
+ public static final TestRule sImplicitClassMaxRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Last);
+
+ public static final TestRule sImplicitInstMinRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.First);
+
+ public static final TestRule sImplicitInstMaxRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.Last);
+
+ public static final String IMPLICIT_CLASS_MIN_RULE_NAME = "sImplicitClassMinRule";
+ public static final String IMPLICIT_CLASS_MAX_RULE_NAME = "sImplicitClassMaxRule";
+ public static final String IMPLICIT_INST_MIN_RULE_NAME = "sImplicitInstMinRule";
+ public static final String IMPLICIT_INST_MAX_RULE_NAME = "sImplicitInstMaxRule";
+
+ /** Keeps track of the runner on the current thread. */
+ private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
+
+ private static RavenwoodAwareTestRunner getCurrentRunner() {
+ var runner = sCurrentRunner.get();
+ if (runner == null) {
+ throw new RuntimeException("Current test runner not set!");
+ }
+ return runner;
+ }
+
+ private final TestClass mTestClsas;
+ private final Runner mRealRunner;
+
+ /** Simple logging method. */
+ private void log(String message) {
+ RavenwoodCommonUtils.log(TAG, "[" + getTestClass() + " @" + this + "] " + message);
+ }
+
+ private Error logAndFail(String message, Throwable innerException) {
+ log(message);
+ log(" Exception=" + innerException);
+ throw new AssertionError(message, innerException);
+ }
+
+ public TestClass getTestClass() {
+ return mTestClsas;
+ }
+
+ /**
+ * Constructor.
+ */
+ public RavenwoodAwareTestRunner(Class<?> testClass) {
+ mTestClsas = new TestClass(testClass);
+
+ /*
+ * If the class has @DisabledOnRavenwood, then we'll delegate to ClassSkippingTestRunner,
+ * which simply skips it.
+ */
+ if (isOnRavenwood() && !shouldRunCassOnRavenwood(mTestClsas.getJavaClass())) {
+ mRealRunner = new ClassSkippingTestRunner(mTestClsas);
+ return;
+ }
+
+ // Find the real runner.
+ final Class<? extends Runner> realRunner;
+ final InnerRunner innerRunnerAnnotation = mTestClsas.getAnnotation(InnerRunner.class);
+ if (innerRunnerAnnotation != null) {
+ realRunner = innerRunnerAnnotation.value();
+ } else {
+ // Default runner.
+ realRunner = BlockJUnit4ClassRunner.class;
+ }
+
+ onRunnerInitializing();
+
+ try {
+ log("Initializing the inner runner: " + realRunner);
+
+ mRealRunner = realRunner.getConstructor(Class.class).newInstance(testClass);
+
+ } catch (InstantiationException | IllegalAccessException
+ | InvocationTargetException | NoSuchMethodException e) {
+ throw logAndFail("Failed to instantiate " + realRunner, e);
+ }
+ }
+
+ /**
+ * Run the bare minimum setup to initialize the wrapped runner.
+ */
+ // This method is called by the ctor, so never make it virtual.
+ private void onRunnerInitializing() {
+ if (!isOnRavenwood()) {
+ return;
+ }
+
+ log("onRunnerInitializing");
+
+ RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClsas);
+
+ // Hook point to allow more customization.
+ runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
+ }
+
+ private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
+ Object instance) {
+ if (!isOnRavenwood()) {
+ return;
+ }
+ log("runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+
+ for (var method : getTestClass().getAnnotatedMethods(annotationClass)) {
+ ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
+
+ var methodDesc = method.getDeclaringClass().getName() + "."
+ + method.getMethod().toString();
+ try {
+ method.getMethod().invoke(instance);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw logAndFail("Caught exception while running method " + methodDesc, e);
+ }
+ }
+ }
+
+ @Override
+ public Description getDescription() {
+ return mRealRunner.getDescription();
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (mRealRunner instanceof ClassSkippingTestRunner) {
+ mRealRunner.run(notifier);
+ return;
+ }
+
+ sCurrentRunner.set(this);
+ try {
+ runWithHooks(getDescription(), Scope.Runner, Order.First,
+ () -> mRealRunner.run(notifier));
+ } finally {
+ sCurrentRunner.remove();
+ }
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (mRealRunner instanceof Filterable r) {
+ r.filter(filter);
+ }
+ }
+
+ @Override
+ public void order(Orderer orderer) throws InvalidOrderingException {
+ if (mRealRunner instanceof Orderable r) {
+ r.order(orderer);
+ }
+ }
+
+ @Override
+ public void sort(Sorter sorter) {
+ if (mRealRunner instanceof Sortable r) {
+ r.sort(sorter);
+ }
+ }
+
+ private Statement updateStatement(Statement base, Description description, Scope scope,
+ Order order) {
+ if (!isOnRavenwood()) {
+ return base;
+ }
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ runWithHooks(description, scope, order, base);
+ }
+ };
+ }
+
+ private void runWithHooks(Description description, Scope scope, Order order, Runnable r) {
+ runWithHooks(description, scope, order, new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ r.run();
+ }
+ });
+ }
+
+ private void runWithHooks(Description description, Scope scope, Order order, Statement s) {
+ Throwable th = null;
+ if (isOnRavenwood()) {
+ Assume.assumeTrue(
+ RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order));
+ }
+ try {
+ s.evaluate();
+ } catch (Throwable t) {
+ th = t;
+ SneakyThrow.sneakyThrow(t);
+ } finally {
+ if (isOnRavenwood()) {
+ RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, th);
+ }
+ }
+ }
+
+ /**
+ * A runner that simply skips a class. It still has to support {@link Filterable}
+ * because otherwise the result still says "SKIPPED" even when it's not included in the
+ * filter.
+ */
+ private static class ClassSkippingTestRunner extends Runner implements Filterable {
+ private final TestClass mTestClass;
+ private final Description mDescription;
+ private boolean mFilteredOut;
+
+ ClassSkippingTestRunner(TestClass testClass) {
+ mTestClass = testClass;
+ mDescription = Description.createTestDescription(
+ testClass.getJavaClass(), testClass.getJavaClass().getSimpleName());
+ mFilteredOut = false;
+ }
+
+ @Override
+ public Description getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (mFilteredOut) {
+ return;
+ }
+ notifier.fireTestSuiteStarted(mDescription);
+ notifier.fireTestIgnored(mDescription);
+ notifier.fireTestSuiteFinished(mDescription);
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (filter.shouldRun(mDescription)) {
+ mFilteredOut = false;
+ } else {
+ throw new NoTestsRemainException();
+ }
+ }
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 74de444..75faafb 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -60,10 +60,12 @@
/**
* When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
- * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
+ * cases where a test is able to pass despite being marked as {@link DisabledOnRavenwood}.
*
* This is typically helpful for internal maintainers discovering tests that had previously
* been ignored, but now have enough Ravenwood-supported functionality to be enabled.
+ *
+ * TODO: Rename it to a more descriptive name.
*/
static final boolean ENABLE_PROBE_IGNORED = "1".equals(
System.getenv("RAVENWOOD_RUN_DISABLED_TESTS"));
@@ -281,7 +283,7 @@
* annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
* an {@link DisabledOnRavenwood} annotation.
*/
- static boolean shouldEnableOnRavenwood(Description description) {
+ public static boolean shouldEnableOnRavenwood(Description description) {
// First, consult any method-level annotations
if (description.isTest()) {
// Stopgap for http://g/ravenwood/EPAD-N5ntxM
@@ -300,20 +302,21 @@
}
// Otherwise, consult any class-level annotations
- final var clazz = description.getTestClass();
+ return shouldRunCassOnRavenwood(description.getTestClass());
+ }
+
+ public static boolean shouldRunCassOnRavenwood(Class<?> clazz) {
if (clazz != null) {
- if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
+ if (clazz.getAnnotation(EnabledOnRavenwood.class) != null) {
return true;
}
- if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
+ if (clazz.getAnnotation(DisabledOnRavenwood.class) != null) {
return false;
}
- if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ if (clazz.getAnnotation(IgnoreUnderRavenwood.class) != null) {
return false;
}
}
-
- // When no annotations have been requested, assume test should be included
return true;
}
@@ -364,6 +367,7 @@
commonPrologue(base, description);
try {
base.evaluate();
+
RavenwoodRuleImpl.logTestRunner("finished", description);
} catch (Throwable t) {
RavenwoodRuleImpl.logTestRunner("failed", description);
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
new file mode 100644
index 0000000..6b80e0c
--- /dev/null
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood;
+
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Provide hook points created by {@link RavenwoodAwareTestRunner}.
+ */
+public class RavenwoodAwareTestRunnerHook {
+ private RavenwoodAwareTestRunnerHook() {
+ }
+
+ /**
+ * Called when a runner starts, befre the inner runner gets a chance to run.
+ */
+ public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+ // No-op on a real device.
+ }
+
+ public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order) {
+ // No-op on a real device.
+ return true;
+ }
+
+ public static void onAfter(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order, Throwable th) {
+ // No-op on a real device.
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 1298023..7b5bc5a 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -21,6 +21,8 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.Arrays;
public class RavenwoodCommonUtils {
@@ -42,12 +44,17 @@
private static final boolean IS_ON_RAVENWOOD = RavenwoodDivergence.isOnRavenwood();
- private static final String RAVEWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
+ private static final String RAVENWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood";
public static final String RAVENWOOD_RESOURCE_APK = "ravenwood-res-apks/ravenwood-res.apk";
+ public static final String RAVENWOOD_EMPTY_RESOURCES_APK =
+ RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk";
+
+ public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
+
// @GuardedBy("sLock")
private static boolean sIntegrityChecked = false;
@@ -74,6 +81,18 @@
return sEnableExtraRuntimeCheck;
}
+ /** Simple logging method. */
+ public static void log(String tag, String message) {
+ // Avoid using Android's Log class, which could be broken for various reasons.
+ // (e.g. the JNI file doesn't exist for whatever reason)
+ System.out.print(tag + ": " + message + "\n");
+ }
+
+ /** Simple logging method. */
+ private void log(String tag, String format, Object... args) {
+ log(tag, String.format(format, args));
+ }
+
/**
* Load the main runtime JNI library.
*/
@@ -178,7 +197,7 @@
*/
public static String getRavenwoodRuntimePath() {
ensureOnRavenwood();
- return RAVEWOOD_RUNTIME_PATH;
+ return RAVENWOOD_RUNTIME_PATH;
}
private static String getRavenwoodRuntimePathInternal() {
@@ -233,4 +252,17 @@
var is = new FileInputStream(fd);
RavenwoodCommonUtils.closeQuietly(is);
}
+
+ public static void ensureIsPublicVoidMethod(Method method, boolean isStatic) {
+ var ok = Modifier.isPublic(method.getModifiers())
+ && (Modifier.isStatic(method.getModifiers()) == isStatic)
+ && (method.getReturnType() == void.class);
+ if (ok) {
+ return; // okay
+ }
+ throw new AssertionError(String.format(
+ "Method %s.%s() expected to be public %svoid",
+ method.getDeclaringClass().getName(), method.getName(),
+ (isStatic ? "static " : "")));
+ }
}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
index 706a055..f894b0e 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
@@ -37,6 +37,9 @@
* Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
*/
public static void ensureRavenwoodInitialized() {
+
+ // TODO Unify it with the initialization code in RavenwoodAwareTestRunnerHook.
+
synchronized (sInitializeLock) {
if (sInitialized) {
return;
diff --git a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index 044239f..b3d3963 100644
--- a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -16,6 +16,7 @@
package com.android.ravenwoodtest.servicestest;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -62,7 +63,9 @@
final SerialManager service = (SerialManager)
mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
final String[] ports = service.getSerialPorts();
- assertEquals(0, ports.length);
+ final String[] refPorts = mRavenwood.getContext().getResources().getStringArray(
+ com.android.internal.R.array.config_serialPorts);
+ assertArrayEquals(refPorts, ports);
}
@Test
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 5cffdec..d8366c5 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -99,6 +99,7 @@
android.util.SparseIntArray
android.util.SparseLongArray
android.util.SparseSetArray
+android.util.StateSet
android.util.StringBuilderPrinter
android.util.TeeWriter
android.util.TimeUtils
@@ -222,9 +223,11 @@
android.content.res.AssetFileDescriptor
android.content.res.AssetManager
android.content.res.AssetManager$Builder
+android.content.res.ColorStateList
android.content.res.ConfigurationBoundResourceCache
android.content.res.Configuration
android.content.res.CompatibilityInfo
+android.content.res.ComplexColor
android.content.res.ConstantState
android.content.res.DrawableCache
android.content.res.Element
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
new file mode 100644
index 0000000..3a7fab3
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+@file:Suppress("ktlint:standard:filename")
+
+package com.android.platform.test.ravenwood.ravenizer
+
+/**
+ * Use it for internal exception that really shouldn't happen.
+ */
+class RavenizerInternalException(message: String) : Exception(message)
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index da9c7d9..e92ef72 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -20,7 +20,7 @@
import com.android.hoststubgen.asm.zipEntryNameToClassName
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
-import com.android.platform.test.ravenwood.ravenizer.adapter.TestRunnerRewritingAdapter
+import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
@@ -177,7 +177,8 @@
* Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
*/
private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
- return TestRunnerRewritingAdapter.shouldProcess(classes, classInternalName)
+ return !classInternalName.shouldByBypassed()
+ && RunnerRewritingAdapter.shouldProcess(classes, classInternalName)
}
private fun processSingleClass(
@@ -191,6 +192,9 @@
lateinit var data: ByteArray
stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
+
+ val classInternalName = zipEntryNameToClassName(entry.name)
+ ?: throw RavenizerInternalException("Unexpected zip entry name: ${entry.name}")
val flags = ClassWriter.COMPUTE_MAXS
val cw = ClassWriter(flags)
var outVisitor: ClassVisitor = cw
@@ -201,7 +205,8 @@
}
// This must be kept in sync with shouldProcessClass.
- outVisitor = TestRunnerRewritingAdapter(allClasses, outVisitor)
+ outVisitor = RunnerRewritingAdapter.maybeApply(
+ classInternalName, allClasses, outVisitor)
cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 0018648..e026e7a 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -15,18 +15,31 @@
*/
package com.android.platform.test.ravenwood.ravenizer
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.startsWithAny
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
import org.objectweb.asm.Type
-val junitTestMethodType = Type.getType(org.junit.Test::class.java)
-val junitRunWithType = Type.getType(org.junit.runner.RunWith::class.java)
+data class TypeHolder(
+ val clazz: Class<*>,
+) {
+ val type = Type.getType(clazz)
+ val desc = type.descriptor
+ val descAsSet = setOf<String>(desc)
+ val internlName = type.internalName
+}
-val junitTestMethodDescriptor = junitTestMethodType.descriptor
-val junitRunWithDescriptor = junitRunWithType.descriptor
+val testAnotType = TypeHolder(org.junit.Test::class.java)
+val ruleAnotType = TypeHolder(org.junit.Rule::class.java)
+val classRuleAnotType = TypeHolder(org.junit.ClassRule::class.java)
+val runWithAnotType = TypeHolder(RunWith::class.java)
+val innerRunnerAnotType = TypeHolder(RavenwoodAwareTestRunner.InnerRunner::class.java)
-val junitTestMethodDescriptors = setOf<String>(junitTestMethodDescriptor)
-val junitRunWithDescriptors = setOf<String>(junitRunWithDescriptor)
+val testRuleType = TypeHolder(TestRule::class.java)
+val ravenwoodTestRunnerType = TypeHolder(RavenwoodAwareTestRunner::class.java)
/**
* Returns true, if a test looks like it's a test class which needs to be processed.
@@ -39,16 +52,44 @@
val cn = classes.findClass(className) ?: return false
- if (cn.findAnyAnnotation(junitRunWithDescriptors) != null) {
+ if (cn.findAnyAnnotation(runWithAnotType.descAsSet) != null) {
return true
}
cn.methods?.forEach { method ->
- if (method.findAnyAnnotation(junitTestMethodDescriptors) != null) {
+ if (method.findAnyAnnotation(testAnotType.descAsSet) != null) {
return true
}
}
+
+ // Check the super class.
if (cn.superName == null) {
return false
}
return isTestLookingClass(classes, cn.superName)
}
+
+fun String.isRavenwoodClass(): Boolean {
+ return this.startsWithAny(
+ "com/android/hoststubgen/",
+ "android/platform/test/ravenwood",
+ "com/android/ravenwood/",
+ "com/android/platform/test/ravenwood/",
+ )
+}
+
+/**
+ * Classes that should never be modified.
+ */
+fun String.shouldByBypassed(): Boolean {
+ if (this.isRavenwoodClass()) {
+ return true
+ }
+ return this.startsWithAny(
+ "java/", // just in case...
+ "javax/",
+ "org/junit/",
+ "org/mockito/",
+ "kotlin/",
+ // TODO -- anything else?
+ )
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
new file mode 100644
index 0000000..25cad02
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer.adapter
+
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner
+import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnnotationValueAsType
+import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.log
+import com.android.hoststubgen.visitors.OPCODE_VERSION
+import com.android.platform.test.ravenwood.ravenizer.RavenizerInternalException
+import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
+import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType
+import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType
+import com.android.platform.test.ravenwood.ravenizer.ruleAnotType
+import com.android.platform.test.ravenwood.ravenizer.runWithAnotType
+import com.android.platform.test.ravenwood.ravenizer.testRuleType
+import org.objectweb.asm.AnnotationVisitor
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.FieldVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Opcodes.ACC_FINAL
+import org.objectweb.asm.Opcodes.ACC_PUBLIC
+import org.objectweb.asm.Opcodes.ACC_STATIC
+import org.objectweb.asm.commons.AdviceAdapter
+import org.objectweb.asm.tree.ClassNode
+
+/**
+ * Class visitor to update the RunWith and inject some necessary rules.
+ *
+ * - Change the @RunWith(RavenwoodAwareTestRunner.class).
+ * - If the original class has a @RunWith(...), then change it to an @OrigRunWith(...).
+ * - Add RavenwoodAwareTestRunner's member rules as junit rules.
+ * - Update the order of the existing JUnit rules to make sure they don't use the MIN or MAX.
+ */
+class RunnerRewritingAdapter private constructor(
+ protected val classes: ClassNodes,
+ nextVisitor: ClassVisitor,
+) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
+ /** Arbitrary cut-off point when deciding whether to change the order or an existing rule.*/
+ val RULE_ORDER_TWEAK_CUTOFF = 1973020500
+
+ /** Current class's internal name */
+ lateinit var classInternalName: String
+
+ /** [ClassNode] for the current class */
+ lateinit var classNode: ClassNode
+
+ /** True if this visitor is generating code. */
+ var isGeneratingCode = false
+
+ /** Run a [block] with [isGeneratingCode] set to true. */
+ private inline fun <T> generateCode(block: () -> T): T {
+ isGeneratingCode = true
+ try {
+ return block()
+ } finally {
+ isGeneratingCode = false
+ }
+ }
+
+ override fun visit(
+ version: Int,
+ access: Int,
+ name: String?,
+ signature: String?,
+ superName: String?,
+ interfaces: Array<out String>?,
+ ) {
+ classInternalName = name!!
+ classNode = classes.getClass(name)
+ if (!isTestLookingClass(classes, name)) {
+ throw RavenizerInternalException("This adapter shouldn't be used for non-test class")
+ }
+ super.visit(version, access, name, signature, superName, interfaces)
+
+ generateCode {
+ injectRunWithAnnotation()
+ if (!classes.hasClassInitializer(classInternalName)) {
+ injectStaticInitializer()
+ }
+ injectRules()
+ }
+ }
+
+ /**
+ * Remove the original @RunWith annotation.
+ */
+ override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
+ if (!isGeneratingCode && runWithAnotType.desc == descriptor) {
+ return null
+ }
+ return super.visitAnnotation(descriptor, visible)
+ }
+
+ override fun visitField(
+ access: Int,
+ name: String,
+ descriptor: String,
+ signature: String?,
+ value: Any?
+ ): FieldVisitor {
+ val fallback = super.visitField(access, name, descriptor, signature, value)
+ if (isGeneratingCode) {
+ return fallback
+ }
+ return FieldRuleOrderRewriter(name, fallback)
+ }
+
+ /** Inject an empty <clinit>. The body will be injected by [visitMethod]. */
+ private fun injectStaticInitializer() {
+ visitMethod(
+ Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
+ CLASS_INITIALIZER_NAME,
+ CLASS_INITIALIZER_DESC,
+ null,
+ null
+ )!!.let { mv ->
+ mv.visitCode()
+ mv.visitInsn(Opcodes.RETURN)
+ mv.visitMaxs(0, 0)
+ mv.visitEnd()
+ }
+ }
+
+ /**
+ * Inject `@RunWith(RavenwoodAwareTestRunner.class)`. If the class already has
+ * a `@RunWith`, then change it to add a `@OrigRunWith`.
+ */
+ private fun injectRunWithAnnotation() {
+ // Extract the original RunWith annotation and its value.
+ val runWith = classNode.findAnyAnnotation(runWithAnotType.descAsSet)
+ val runWithClass = runWith?.let { an ->
+ findAnnotationValueAsType(an, "value")
+ }
+
+ if (runWith != null) {
+ if (runWithClass == ravenwoodTestRunnerType.type) {
+ // It already uses RavenwoodTestRunner. We'll just keep it, but we need to
+ // inject it again because the original one is removed by visitAnnotation().
+ log.d("Class ${classInternalName.toHumanReadableClassName()}" +
+ " already uses RavenwoodTestRunner.")
+ visitAnnotation(runWithAnotType.desc, true)!!.let { av ->
+ av.visit("value", ravenwoodTestRunnerType)
+ av.visitEnd()
+ }
+ return
+ }
+ if (runWithClass == null) {
+ throw ClassParseException("@RunWith annotation doesn't have a property \"value\""
+ + " in class ${classInternalName.toHumanReadableClassName()}")
+ }
+
+ // Inject an @OrigRunWith.
+ visitAnnotation(innerRunnerAnotType.desc, true)!!.let { av ->
+ av.visit("value", runWithClass)
+ av.visitEnd()
+ }
+ }
+
+ // Inject a @RunWith(RavenwoodAwareTestRunner.class).
+ visitAnnotation(runWithAnotType.desc, true)!!.let { av ->
+ av.visit("value", ravenwoodTestRunnerType.type)
+ av.visitEnd()
+ }
+ log.d("Processed ${classInternalName.toHumanReadableClassName()}")
+ }
+
+ /*
+ Generate the fields and the ctor, which should looks like this:
+
+ public static final org.junit.rules.TestRule sRavenwoodImplicitClassMinRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #49(#50=I#51)
+ org.junit.ClassRule(
+ order=-2147483648
+ )
+
+ public static final org.junit.rules.TestRule sRavenwoodImplicitClassMaxRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #49(#50=I#52)
+ org.junit.ClassRule(
+ order=2147483647
+ )
+
+ public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMinRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #53(#50=I#51)
+ org.junit.Rule(
+ order=-2147483648
+ )
+
+ public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMaxRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #53(#50=I#52)
+ org.junit.Rule(
+ order=2147483647
+ )
+ */
+
+ val sRavenwood_ClassRuleMin = "sRavenwood_ClassRuleMin"
+ val sRavenwood_ClassRuleMax = "sRavenwood_ClassRuleMax"
+ val mRavenwood_InstRuleMin = "mRavenwood_InstRuleMin"
+ val mRavenwood_InstRuleMax = "mRavenwood_InstRuleMax"
+
+ private fun injectRules() {
+ injectRule(sRavenwood_ClassRuleMin, true, Integer.MIN_VALUE)
+ injectRule(sRavenwood_ClassRuleMax, true, Integer.MAX_VALUE)
+ injectRule(mRavenwood_InstRuleMin, false, Integer.MIN_VALUE)
+ injectRule(mRavenwood_InstRuleMax, false, Integer.MAX_VALUE)
+ }
+
+ private fun injectRule(fieldName: String, isStatic: Boolean, order: Int) {
+ visitField(
+ ACC_PUBLIC or ACC_FINAL or (if (isStatic) ACC_STATIC else 0),
+ fieldName,
+ testRuleType.desc,
+ null,
+ null,
+ ).let { fv ->
+ val anot = if (isStatic) { classRuleAnotType } else { ruleAnotType }
+ fv.visitAnnotation(anot.desc, true).let {
+ it.visit("order", order)
+ it.visitEnd()
+ }
+ fv.visitEnd()
+ }
+ }
+
+ override fun visitMethod(
+ access: Int,
+ name: String,
+ descriptor: String,
+ signature: String?,
+ exceptions: Array<String>?,
+ ): MethodVisitor {
+ val next = super.visitMethod(access, name, descriptor, signature, exceptions)
+ if (name == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) {
+ return ClassInitializerVisitor(
+ access, name, descriptor, signature, exceptions, next)
+ }
+ if (name == CTOR_NAME) {
+ return ConstructorVisitor(
+ access, name, descriptor, signature, exceptions, next)
+ }
+ return next
+ }
+
+ /*
+
+ static {};
+ descriptor: ()V
+ flags: (0x0008) ACC_STATIC
+ Code:
+ stack=1, locals=0, args_size=0
+ 0: getstatic #36 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule;
+ 3: putstatic #39 // Field sRavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule;
+ 6: getstatic #42 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule;
+ 9: putstatic #45 // Field sRavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule;
+ 12: return
+ LineNumberTable:
+ line 33: 0
+ line 36: 6
+ */
+ private inner class ClassInitializerVisitor(
+ access: Int,
+ val name: String,
+ val descriptor: String,
+ signature: String?,
+ exceptions: Array<String>?,
+ next: MethodVisitor?,
+ ) : MethodVisitor(OPCODE_VERSION, next) {
+ override fun visitCode() {
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_CLASS_MIN_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTSTATIC,
+ classInternalName,
+ sRavenwood_ClassRuleMin,
+ testRuleType.desc
+ )
+
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_CLASS_MAX_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTSTATIC,
+ classInternalName,
+ sRavenwood_ClassRuleMax,
+ testRuleType.desc
+ )
+
+ super.visitCode()
+ }
+ }
+
+ /*
+ public com.android.ravenwoodtest.bivalenttest.runnertest.RavenwoodRunnerTest();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ 0: aload_0
+ 1: invokespecial #1 // Method java/lang/Object."<init>":()V
+ 4: aload_0
+ 5: getstatic #7 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule;
+ 8: putfield #13 // Field sRavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule;
+ 11: aload_0
+ 12: getstatic #18 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule;
+ 15: putfield #21 // Field sRavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule;
+ 18: return
+ LineNumberTable:
+ line 31: 0
+ line 38: 4
+ line 41: 11
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 19 0 this Lcom/android/ravenwoodtest/bivalenttest/runnertest/RavenwoodRunnerTest;
+ */
+ private inner class ConstructorVisitor(
+ access: Int,
+ name: String,
+ descriptor: String,
+ signature: String?,
+ exceptions: Array<String>?,
+ next: MethodVisitor?,
+ ) : AdviceAdapter(OPCODE_VERSION, next, ACC_ENUM, name, descriptor) {
+ override fun onMethodEnter() {
+ visitVarInsn(ALOAD, 0)
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_INST_MIN_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTFIELD,
+ classInternalName,
+ mRavenwood_InstRuleMin,
+ testRuleType.desc
+ )
+
+ visitVarInsn(ALOAD, 0)
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_INST_MAX_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTFIELD,
+ classInternalName,
+ mRavenwood_InstRuleMax,
+ testRuleType.desc
+ )
+ }
+ }
+
+ /**
+ * Rewrite "order" of the existing junit rules to make sure no rules use a MAX or MIN order.
+ *
+ * Currently, we do it a hacky way -- use an arbitrary cut-off point, and if the order
+ * is larger than that, decrement by 1, and if it's smaller than the negative cut-off point,
+ * increment it by 1.
+ *
+ * (or the arbitrary number is already used.... then we're unlucky, let's change the cut-off
+ * point.)
+ */
+ private inner class FieldRuleOrderRewriter(
+ val fieldName: String,
+ next: FieldVisitor,
+ ) : FieldVisitor(OPCODE_VERSION, next) {
+ override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
+ val fallback = super.visitAnnotation(descriptor, visible)
+ if (descriptor != ruleAnotType.desc && descriptor != classRuleAnotType.desc) {
+ return fallback
+ }
+ return RuleOrderRewriter(fallback)
+ }
+
+ private inner class RuleOrderRewriter(
+ next: AnnotationVisitor,
+ ) : AnnotationVisitor(OPCODE_VERSION, next) {
+ override fun visit(name: String?, origValue: Any?) {
+ if (name != "order") {
+ return super.visit(name, origValue)
+ }
+ var order = origValue as Int
+ if (order == RULE_ORDER_TWEAK_CUTOFF || order == -RULE_ORDER_TWEAK_CUTOFF) {
+ // Oops. If this happens, we'll need to change RULE_ORDER_TWEAK_CUTOFF.
+ // Or, we could scan all the rules in the target jar and find an unused number.
+ // Because rules propagate to subclasses, we'll at least check all the
+ // super classes of the current class.
+ throw RavenizerInternalException(
+ "OOPS: Field $classInternalName.$fieldName uses $order."
+ + " We can't update it.")
+ }
+ if (order > RULE_ORDER_TWEAK_CUTOFF) {
+ order -= 1
+ }
+ if (order < -RULE_ORDER_TWEAK_CUTOFF) {
+ order += 1
+ }
+ super.visit(name, order)
+ }
+ }
+ }
+
+ companion object {
+ fun shouldProcess(classes: ClassNodes, className: String): Boolean {
+ return isTestLookingClass(classes, className)
+ }
+
+ fun maybeApply(
+ className: String,
+ classes: ClassNodes,
+ nextVisitor: ClassVisitor,
+ ): ClassVisitor {
+ if (!shouldProcess(classes, className)) {
+ return nextVisitor
+ } else {
+ return RunnerRewritingAdapter(classes, nextVisitor)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
deleted file mode 100644
index c539908..0000000
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer.adapter
-
-import com.android.hoststubgen.asm.ClassNodes
-import com.android.hoststubgen.visitors.OPCODE_VERSION
-import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
-import org.objectweb.asm.ClassVisitor
-
-/**
- * Class visitor to rewrite the test runner for Ravenwood
- *
- * TODO: Implement it.
- */
-class TestRunnerRewritingAdapter(
- protected val classes: ClassNodes,
- nextVisitor: ClassVisitor,
-) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
- companion object {
- /**
- * Returns true if a target class is interesting to this adapter.
- */
- fun shouldProcess(classes: ClassNodes, className: String): Boolean {
- return isTestLookingClass(classes, className)
- }
- }
-}
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index 82c2038..dbf144f 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -56,16 +56,11 @@
}
}
- @android.ravenwood.annotation.RavenwoodReplace
private static String[] getSerialPorts(Context context) {
return context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
- private static String[] getSerialPorts$ravenwood(Context context) {
- return new String[0];
- }
-
public static class Lifecycle extends SystemService {
private SerialService mService;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f1bdc05..b599a2f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12158,7 +12158,7 @@
opts.dumpProto = true;
} else if ("--logstats".equals(opt)) {
opts.mDumpAllocatorStats = true;
- } else if ("-h".equals(opt)) {
+ } else if ("-h".equals(opt) || "--help".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
pw.println(" -d: include dalvik details.");
@@ -12172,6 +12172,8 @@
pw.println(" processes that have loaded that package.");
pw.println(" --checkin: dump data for a checkin");
pw.println(" --proto: dump data to proto");
+ pw.println(" --logstats: log native allocator statistics.");
+ pw.println(" --unreachable: dump unreachable native memory with libmemunreachable.");
pw.println("If [process] is specified it can be the name or ");
pw.println("pid of a specific process to dump.");
return;
diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp
index 0294ffe..ceba01e4 100644
--- a/services/core/java/com/android/server/am/Android.bp
+++ b/services/core/java/com/android/server/am/Android.bp
@@ -9,3 +9,10 @@
name: "am_flags_lib",
aconfig_declarations: "am_flags",
}
+
+java_aconfig_library {
+ name: "am_flags_host_lib",
+ host_supported: true,
+ libs: ["fake_device_config"],
+ aconfig_declarations: "am_flags",
+}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index e4c65bd2..8c5152f 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -2307,7 +2307,7 @@
return;
}
- final int userId = mContext.getUserId();
+ final int userId = ActivityManager.getCurrentUser();
final boolean isNotGame = Arrays.stream(packages).noneMatch(
p -> isPackageGame(p, userId));
synchronized (mUidObserverLock) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ca907c5..1cf9935 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1714,6 +1714,10 @@
sendIILMsg(MSG_IIL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, codec, address, delayMs);
}
+ /*package*/ void setHearingAidTimeout(String address, int delayMs) {
+ sendLMsg(MSG_IL_BT_HEARING_AID_TIMEOUT, SENDMSG_QUEUE, address, delayMs);
+ }
+
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
synchronized (mDeviceStateLock) {
mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
@@ -1959,6 +1963,13 @@
(String) msg.obj, msg.arg1, msg.arg2);
}
break;
+ case MSG_IL_BT_HEARING_AID_TIMEOUT:
+ // msg.obj == address of Hearing Aid device
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.onMakeHearingAidDeviceUnavailableNow(
+ (String) msg.obj);
+ }
+ break;
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
final Pair<Integer, Boolean> codecAndChanged = mBtHelper.getCodecWithFallback(
@@ -2234,6 +2245,7 @@
private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58;
private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59;
private static final int MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE = 60;
+ private static final int MSG_IL_BT_HEARING_AID_TIMEOUT = 61;
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
@@ -2246,6 +2258,7 @@
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
case MSG_CHECK_MUTE_MUSIC:
+ case MSG_IL_BT_HEARING_AID_TIMEOUT:
return true;
default:
return false;
@@ -2330,6 +2343,7 @@
case MSG_IL_BTA2DP_TIMEOUT:
case MSG_IIL_BTLEAUDIO_TIMEOUT:
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
+ case MSG_IL_BT_HEARING_AID_TIMEOUT:
if (sLastDeviceConnectMsgTime >= time) {
// add a little delay to make sure messages are ordered as expected
time = sLastDeviceConnectMsgTime + 30;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 082dca6..a9bff8b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1050,6 +1050,11 @@
}
}
+ /*package*/ void onMakeHearingAidDeviceUnavailableNow(String address) {
+ synchronized (mDevicesLock) {
+ makeHearingAidDeviceUnavailable(address);
+ }
+ }
/**
* Goes over all connected LE Audio devices in the provided group ID and
@@ -1902,12 +1907,10 @@
.set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
.record();
if (toRemove.size() > 0) {
- /*final int delay = */
- checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
+ final int delay = checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
toRemove.stream().forEach(deviceAddress ->
- // TODO delay not used?
- makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+ makeHearingAidDeviceUnavailableLater(deviceAddress, delay)
);
}
}
@@ -2498,6 +2501,15 @@
mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
+ @GuardedBy("mDevicesLock")
+ private void makeHearingAidDeviceUnavailableLater(
+ String address, int delayMs) {
+ // the device will be made unavailable later, so consider it disconnected right away
+ mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
+ // send the delayed message to make the device unavailable later
+ mDeviceBroker.setHearingAidTimeout(address, delayMs);
+ }
+
/**
* Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
* Visibility by APM plays no role
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index c5180af..5283edd 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -33,6 +33,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IntArray;
@@ -190,6 +191,7 @@
mIsUpdateDeferred = true;
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "audioserver_permission_update");
try {
for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
var newPerms = getUidsHoldingPerm(i);
@@ -203,6 +205,8 @@
mDest = null;
// We didn't necessarily finish
mIsUpdateDeferred = true;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53b04df..14dca4e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -284,11 +284,16 @@
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@@ -785,6 +790,8 @@
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
private final Executor mAudioServerLifecycleExecutor;
+ private final ConcurrentLinkedQueue<Future> mScheduledPermissionTasks =
+ new ConcurrentLinkedQueue();
private IMediaProjectionManager mProjectionService; // to validate projection token
@@ -1092,7 +1099,8 @@
public Lifecycle(Context context) {
super(context);
- var audioserverLifecycleExecutor = Executors.newSingleThreadExecutor();
+ var audioserverLifecycleExecutor = Executors.newSingleThreadScheduledExecutor(
+ (Runnable r) -> new Thread(r, "audioserver_lifecycle"));
var audioPolicyFacade = new DefaultAudioPolicyFacade(audioserverLifecycleExecutor);
mService = new AudioService(context,
AudioSystemAdapter.getDefaultAdapter(),
@@ -1222,34 +1230,6 @@
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
mBroadcastHandlerThread.start();
- // Listen to permission invalidations for the PermissionProvider
- if (audioserverPermissions()) {
- final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
- mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
- new Runnable() {
- // Roughly chosen to be long enough to suppress the autocork behavior
- // of the permission cache (50ms), and longer than the task could reasonably
- // take, even with many packages and users, while not introducing visible
- // permission leaks - since the app needs to restart, and trigger an action
- // which requires permissions from audioserver before this delay.
- // For RECORD_AUDIO, we are additionally protected by appops.
- final long UPDATE_DELAY_MS = 110;
- final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
- @Override
- public void run() {
- var currentTime = SystemClock.uptimeMillis();
- if (currentTime > scheduledUpdateTimestamp.get()) {
- scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
- broadcastHandler.postAtTime( () ->
- mAudioServerLifecycleExecutor.execute(mPermissionProvider
- ::onPermissionStateChanged),
- currentTime + UPDATE_DELAY_MS
- );
- }
- }
- });
- }
-
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1717,8 +1697,10 @@
public void onSystemReady() {
mSystemReady = true;
+ if (audioserverPermissions()) {
+ setupPermissionListener();
+ }
scheduleLoadSoundEffects();
-
mDeviceBroker.onSystemReady();
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -10608,6 +10590,63 @@
}
}
+ /* Listen to permission invalidations for the PermissionProvider */
+ private void setupPermissionListener() {
+ // Roughly chosen to be long enough to suppress the autocork behavior of the permission
+ // cache (50ms), while not introducing visible permission leaks - since the app needs to
+ // restart, and trigger an action which requires permissions from audioserver before this
+ // delay. For RECORD_AUDIO, we are additionally protected by appops.
+ final long UPDATE_DELAY_MS = 60;
+ // instanceof to simplify the construction requirements of AudioService for testing: no
+ // delayed execution during unit tests.
+ if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) {
+ // We schedule and add from a this callback thread only (serially), so the task order on
+ // the serial executor matches the order on the task list. This list should almost
+ // always have only two elements, except in cases of serious system contention.
+ Runnable task = () -> mScheduledPermissionTasks.add(exec.schedule(() -> {
+ try {
+ // Clean up completed tasks before us to bound the queue length. Cancel any
+ // pending permission refresh tasks, after our own, since we are about to
+ // fulfill all of them. We must be the first non-completed task in the
+ // queue, since the execution order matches the queue order. Note, this
+ // task is the only writer on elements in the queue, and the task is
+ // serialized, so
+ // => no in-flight cancellation
+ // => exists at least one non-completed task (ourselves)
+ // => the queue is non-empty (only completed tasks removed)
+ final var iter = mScheduledPermissionTasks.iterator();
+ while (iter.next().isDone()) {
+ iter.remove();
+ }
+ // iter is on the first element which is not completed (us)
+ while (iter.hasNext()) {
+ if (!iter.next().cancel(false)) {
+ throw new AssertionError(
+ "Cancel should be infallible since we" +
+ "cancel from the executor");
+ }
+ iter.remove();
+ }
+ mPermissionProvider.onPermissionStateChanged();
+ } catch (Exception e) {
+ // Handle executor routing exceptions to nowhere
+ Thread.getDefaultUncaughtExceptionHandler()
+ .uncaughtException(Thread.currentThread(), e);
+ }
+ },
+ UPDATE_DELAY_MS,
+ TimeUnit.MILLISECONDS));
+ mAudioSystem.listenForSystemPropertyChange(
+ PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ task);
+ task.run();
+ } else {
+ mAudioSystem.listenForSystemPropertyChange(
+ PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ () -> mAudioServerLifecycleExecutor.execute(
+ mPermissionProvider::onPermissionStateChanged));
+ }
+ }
//==========================================================================================
// Audio Focus
@@ -10760,6 +10799,11 @@
final long token = Binder.clearCallingIdentity();
try {
+ //TODO move inside HardeningEnforcer after refactor that moves permission checks
+ // in the blockFocusMethod
+ if (permissionOverridesCheck) {
+ mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, durationHint, uid);
+ }
if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid,
HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS,
clientId, durationHint, callingPackageName, attributionTag, sdk)) {
@@ -14663,6 +14707,20 @@
return activeAssistantUids;
}
+ @Override
+ /** @see AudioManager#permissionUpdateBarrier() */
+ public void permissionUpdateBarrier() {
+ for (var x : List.copyOf(mScheduledPermissionTasks)) {
+ try {
+ x.get();
+ } catch (CancellationException e) {
+ // Task completed
+ } catch (InterruptedException | ExecutionException e) {
+ Log.wtf(TAG, "Exception which should never occur", e);
+ }
+ }
+ }
+
List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
return mDeviceBroker.getDeviceIdentityAddresses(device);
}
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
index 8ae04ac..3c509bc 100644
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -31,7 +31,9 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
+import com.android.modules.expresslog.Counter;
import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
@@ -55,6 +57,30 @@
final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS,
"Hardening enforcement");
+ // capacity = 4 for each of the focus request types
+ static final SparseArray<String> METRIC_COUNTERS_FOCUS_DENIAL = new SparseArray<>(4);
+ static final SparseArray<String> METRIC_COUNTERS_FOCUS_GRANT = new SparseArray<>(4);
+
+ static {
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN,
+ "media_audio.value_audio_focus_gain_granted");
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ "media_audio.value_audio_focus_gain_transient_granted");
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ "media_audio.value_audio_focus_gain_transient_duck_granted");
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+ "media_audio.value_audio_focus_gain_transient_excl_granted");
+
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN,
+ "media_audio.value_audio_focus_gain_appops_denial");
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ "media_audio.value_audio_focus_gain_transient_appops_denial");
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ "media_audio.value_audio_focus_gain_transient_duck_appops_denial");
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+ "media_audio.value_audio_focus_gain_transient_excl_appops_denial");
+ }
+
/**
* Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
*/
@@ -141,29 +167,49 @@
packageName = getPackNameForUid(callingUid);
}
+ boolean blocked = true;
if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) {
if (DEBUG) {
Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking");
}
- return false;
+ blocked = false;
} else if (targetSdk < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
if (DEBUG) {
Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking due to sdk="
+ targetSdk);
}
+ blocked = false;
+ }
+
+ metricsLogFocusReq(blocked, durationHint, callingUid);
+
+ if (!blocked) {
return false;
}
String errorMssg = "Focus request DENIED for uid:" + callingUid
+ " clientId:" + clientId + " req:" + durationHint
+ " procState:" + mActivityManager.getUidProcessState(callingUid);
-
- // TODO metrics
mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG);
return true;
}
+ /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) {
+ final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq)
+ : METRIC_COUNTERS_FOCUS_GRANT.get(focusReq);
+ if (TextUtils.isEmpty(metricId)) {
+ Slog.e(TAG, "Bad string for focus metrics gain:" + focusReq + " blocked:" + blocked);
+ return;
+ }
+ try {
+ Counter.logIncrementWithUid(metricId, callingUid);
+ } catch (Exception e) {
+ Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq
+ + " from uid:" + callingUid, e);
+ }
+ }
+
private String getPackNameForUid(int uid) {
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index cf677d5..7b1186c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -80,13 +80,16 @@
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @NonNull
+ private final FaceUtils mBiometricUtils;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback,
+ @NonNull FaceUtils biometricUtils) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
@@ -95,6 +98,7 @@
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
+ mBiometricUtils = biometricUtils;
}
@Override
@@ -167,8 +171,7 @@
} else {
currentUserId = client.getTargetUserId();
}
- final CharSequence name = FaceUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
+ final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId);
final Face face = new Face(name, enrollmentId, mSensorId);
handleResponse(FaceEnrollClient.class, (c) -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 3eecc6d..d4ec573 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -60,7 +60,6 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.face.FaceService;
-import com.android.server.biometrics.sensors.face.FaceUtils;
import java.io.IOException;
import java.util.ArrayList;
@@ -85,6 +84,7 @@
private final int mMaxTemplatesPerUser;
private final boolean mDebugConsent;
private final @android.hardware.face.FaceEnrollOptions.EnrollReason int mEnrollReason;
+ private final BiometricUtils<Face> mBiometricUtils;
private final ClientMonitorCallback mPreviewHandleDeleterCallback =
new ClientMonitorCallback() {
@@ -107,7 +107,8 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
int maxTemplatesPerUser, boolean debugConsent,
android.hardware.face.FaceEnrollOptions options,
- @NonNull AuthenticationStateListeners authenticationStateListeners) {
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
+ @NonNull BiometricUtils<Face> biometricUtils) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
@@ -122,6 +123,7 @@
mDebugConsent = debugConsent;
mDisabledFeatures = disabledFeatures;
mPreviewSurface = previewSurface;
+ mBiometricUtils = biometricUtils;
Slog.w(TAG, "EnrollOptions "
+ android.hardware.face.FaceEnrollOptions.enrollReasonToString(
options.getEnrollReason()));
@@ -144,7 +146,7 @@
@Override
protected boolean hasReachedEnrollmentLimit() {
- return FaceUtils.getInstance(getSensorId()).getBiometricsForUser(getContext(),
+ return mBiometricUtils.getBiometricsForUser(getContext(),
getTargetUserId()).size() >= mMaxTemplatesPerUser;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index 964bf6c..c27b7c4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -30,7 +30,6 @@
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
import com.android.server.biometrics.sensors.RemovalClient;
-import com.android.server.biometrics.sensors.face.FaceUtils;
import java.util.List;
import java.util.Map;
@@ -75,7 +74,7 @@
@Override
protected void onAddUnknownTemplate(int userId,
@NonNull BiometricAuthenticator.Identifier identifier) {
- FaceUtils.getInstance(getSensorId()).addBiometricForUser(
+ mBiometricUtils.addBiometricForUser(
getContext(), getTargetUserId(), (Face) identifier);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index f0a4189..bb213bf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -72,7 +72,6 @@
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
-import com.android.server.biometrics.sensors.face.FaceUtils;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
@@ -326,8 +325,8 @@
}
if (Build.isDebuggable()) {
- BiometricUtils<Face> utils = FaceUtils.getInstance(
- mFaceSensors.keyAt(0));
+ BiometricUtils<Face> utils = mFaceSensors.get(
+ mFaceSensors.keyAt(0)).getFaceUtilsInstance();
for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
List<Face> enrollments = utils.getBiometricsForUser(mContext, user.id);
Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
@@ -386,7 +385,7 @@
new InvalidationRequesterClient<>(mContext, userId, sensorId,
BiometricLogger.ofUnknown(mContext),
mBiometricContext,
- FaceUtils.getInstance(sensorId));
+ mFaceSensors.get(sensorId).getFaceUtilsInstance());
scheduleForSensor(sensorId, client);
});
}
@@ -415,7 +414,8 @@
@NonNull
@Override
public List<Face> getEnrolledFaces(int sensorId, int userId) {
- return FaceUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId);
+ return mFaceSensors.get(sensorId).getFaceUtilsInstance()
+ .getBiometricsForUser(mContext, userId);
}
@Override
@@ -497,13 +497,14 @@
final FaceEnrollClient client = new FaceEnrollClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
+ opPackageName, id, mFaceSensors.get(sensorId).getFaceUtilsInstance(),
+ disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent, options,
- mAuthenticationStateListeners);
+ mAuthenticationStateListeners,
+ mFaceSensors.get(sensorId).getFaceUtilsInstance());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
@@ -615,7 +616,7 @@
@Override
public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- final List<Face> faces = FaceUtils.getInstance(sensorId)
+ final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId);
final int[] faceIds = new int[faces.size()];
for (int i = 0; i < faces.size(); i++) {
@@ -632,7 +633,7 @@
final FaceRemovalClient client = new FaceRemovalClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
- opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+ opPackageName, mFaceSensors.get(sensorId).getFaceUtilsInstance(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
@@ -666,7 +667,7 @@
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
- final List<Face> faces = FaceUtils.getInstance(sensorId)
+ final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
Slog.w(getTag(), "Ignoring setFeature, no templates enrolled for user: " + userId);
@@ -687,7 +688,7 @@
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
mHandler.post(() -> {
mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
- final List<Face> faces = FaceUtils.getInstance(sensorId)
+ final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
Slog.w(getTag(), "Ignoring getFeature, no templates enrolled for user: " + userId);
@@ -727,7 +728,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext,
- FaceUtils.getInstance(sensorId),
+ mFaceSensors.get(sensorId).getFaceUtilsInstance(),
mFaceSensors.get(sensorId).getAuthenticatorIds());
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
@@ -768,7 +769,7 @@
JSONArray sets = new JSONArray();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
final int userId = user.getUserHandle().getIdentifier();
- final int c = FaceUtils.getInstance(sensorId)
+ final int c = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId).size();
JSONObject set = new JSONObject();
set.put("id", userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index b0e7575..6f95349 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -158,7 +158,7 @@
Slog.e(TAG, "Face sensor hardware unavailable.");
mCurrentSession = null;
}
- });
+ }, getFaceUtilsInstance());
return Sensor.this.getStartUserClient(resultController, sensorId,
newUserId, provider);
@@ -280,8 +280,7 @@
final long userToken = proto.start(SensorStateProto.USER_STATES);
proto.write(UserStateProto.USER_ID, userId);
proto.write(UserStateProto.NUM_ENROLLED,
- FaceUtils.getInstance(mSensorProperties.sensorId)
- .getBiometricsForUser(mContext, userId).size());
+ getFaceUtilsInstance().getBiometricsForUser(mContext, userId).size());
proto.end(userToken);
}
@@ -358,4 +357,8 @@
Supplier<AidlSession> lazySession) {
mLazySession = lazySession;
}
+
+ public FaceUtils getFaceUtilsInstance() {
+ return FaceUtils.getInstance(mSensorProperties.sensorId);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index 9a4c29d..444a6d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -159,6 +159,11 @@
}
@Override
+ public FaceUtils getFaceUtilsInstance() {
+ return FaceUtils.getLegacyInstance(getSensorProperties().sensorId);
+ }
+
+ @Override
protected LockoutTracker getLockoutTracker(boolean forAuth) {
return mLockoutTracker;
}
@@ -180,7 +185,8 @@
mLockoutTracker,
mLockoutResetDispatcher,
mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback,
+ getFaceUtilsInstance());
}
private IBiometricsFace getIBiometricsFace() {
@@ -247,8 +253,7 @@
return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
- !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
- getContext(), userId).isEmpty(),
+ !getFaceUtilsInstance().getBiometricsForUser(getContext(), userId).isEmpty(),
getAuthenticatorIds());
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 6d1715f..80b7cde 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -80,13 +80,16 @@
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @NonNull
+ private final FingerprintUtils mBiometricUtils;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback,
+ @NonNull FingerprintUtils biometricUtils) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
@@ -95,6 +98,7 @@
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
+ mBiometricUtils = biometricUtils;
}
@Override
@@ -158,8 +162,7 @@
} else {
currentUserId = client.getTargetUserId();
}
- final CharSequence name = FingerprintUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
+ final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId);
final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
enrollmentId, mSensorId);
handleResponse(FingerprintEnrollClient.class, (c) -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 1fc5179..40b8a45 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -81,7 +81,7 @@
@Override
protected void onAddUnknownTemplate(int userId,
@NonNull BiometricAuthenticator.Identifier identifier) {
- FingerprintUtils.getInstance(getSensorId()).addBiometricForUser(
+ mBiometricUtils.addBiometricForUser(
getContext(), getTargetUserId(), (Fingerprint) identifier);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 12baf00..9edaa4e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -79,7 +79,6 @@
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
@@ -354,8 +353,9 @@
}
if (Build.isDebuggable()) {
- BiometricUtils<Fingerprint> utils = FingerprintUtils.getInstance(
- mFingerprintSensors.keyAt(0));
+ final int sensorId = mFingerprintSensors.keyAt(0);
+ final BiometricUtils<Fingerprint> utils = mFingerprintSensors.get(sensorId)
+ .getFingerprintUtilsInstance();
for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
List<Fingerprint> enrollments = utils.getBiometricsForUser(mContext, user.id);
Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
@@ -442,7 +442,7 @@
new InvalidationRequesterClient<>(mContext, userId, sensorId,
BiometricLogger.ofUnknown(mContext),
mBiometricContext,
- FingerprintUtils.getInstance(sensorId));
+ mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance());
scheduleForSensor(sensorId, client);
});
}
@@ -507,7 +507,7 @@
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FingerprintUtils.getInstance(sensorId),
+ opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
@@ -638,8 +638,8 @@
public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, int userId,
@NonNull String opPackageName) {
- final List<Fingerprint> fingers = FingerprintUtils.getInstance(sensorId)
- .getBiometricsForUser(mContext, userId);
+ final List<Fingerprint> fingers = mFingerprintSensors.get(sensorId)
+ .getFingerprintUtilsInstance().getBiometricsForUser(mContext, userId);
final int[] fingerIds = new int[fingers.size()];
for (int i = 0; i < fingers.size(); i++) {
fingerIds[i] = fingers.get(i).getBiometricId();
@@ -655,11 +655,10 @@
final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext,
+ opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
+ sensorId, createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -683,7 +682,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext,
- FingerprintUtils.getInstance(sensorId),
+ mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
@@ -706,14 +705,15 @@
@Override
public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
- FingerprintUtils.getInstance(sensorId)
+ mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
.renameBiometricForUser(mContext, userId, fingerId, name);
}
@NonNull
@Override
public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
- return FingerprintUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId);
+ return mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
+ .getBiometricsForUser(mContext, userId);
}
@Override
@@ -842,7 +842,7 @@
JSONArray sets = new JSONArray();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
final int userId = user.getUserHandle().getIdentifier();
- final int c = FingerprintUtils.getInstance(sensorId)
+ final int c = mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
.getBiometricsForUser(mContext, userId).size();
JSONObject set = new JSONObject();
set.put("id", userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 1c6dfe0..d12d7b2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -170,7 +170,7 @@
"Fingerprint sensor hardware unavailable.");
mCurrentSession = null;
}
- });
+ }, getFingerprintUtilsInstance());
return Sensor.this.getStartUserClient(resultController, sensorId,
newUserId);
@@ -187,7 +187,7 @@
+ halInterfaceVersion);
mCurrentSession = new AidlSession(halInterfaceVersion,
newSession, userIdStarted, resultController);
- if (FingerprintUtils.getInstance(sensorId)
+ if (getFingerprintUtilsInstance()
.isInvalidationInProgress(mContext, userIdStarted)) {
Slog.w(TAG,
"Scheduling unfinished invalidation request for "
@@ -307,9 +307,8 @@
final long userToken = proto.start(SensorStateProto.USER_STATES);
proto.write(UserStateProto.USER_ID, userId);
- proto.write(UserStateProto.NUM_ENROLLED,
- FingerprintUtils.getInstance(mSensorProperties.sensorId)
- .getBiometricsForUser(mContext, userId).size());
+ proto.write(UserStateProto.NUM_ENROLLED, getFingerprintUtilsInstance()
+ .getBiometricsForUser(mContext, userId).size());
proto.end(userToken);
}
@@ -386,4 +385,8 @@
public FingerprintProvider getProvider() {
return mProvider;
}
+
+ public FingerprintUtils getFingerprintUtilsInstance() {
+ return FingerprintUtils.getInstance(mSensorProperties.sensorId);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 3214b6d..8f52d00 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -148,6 +148,11 @@
}
@Override
+ public FingerprintUtils getFingerprintUtilsInstance() {
+ return FingerprintUtils.getLegacyInstance(getSensorProperties().sensorId);
+ }
+
+ @Override
@Nullable
@VisibleForTesting
protected AidlSession getSessionForUser(int userId) {
@@ -186,7 +191,8 @@
mLockoutTracker,
mLockoutResetDispatcher,
mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback,
+ getFingerprintUtilsInstance());
}
@VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
@@ -266,8 +272,7 @@
() -> getSession().getSession(), newUserId, TAG,
getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
getBiometricContext(), () -> mCurrentUserId,
- !FingerprintUtils.getInstance(getSensorProperties().sensorId)
- .getBiometricsForUser(getContext(),
+ !getFingerprintUtilsInstance().getBiometricsForUser(getContext(),
newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
mUserStartedCallback);
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index c31d1d8..d909004 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1500,10 +1500,18 @@
}
private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) {
- Vote vote = info != null && info.layoutLimitedRefreshRate != null
- ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
- info.layoutLimitedRefreshRate.max) : null;
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
+ Vote refreshRateVote = null;
+ Vote frameRateVote = null;
+ if (info != null && info.layoutLimitedRefreshRate != null) {
+ refreshRateVote = Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
+ info.layoutLimitedRefreshRate.max);
+ frameRateVote = Vote.forRenderFrameRates(info.layoutLimitedRefreshRate.min,
+ info.layoutLimitedRefreshRate.max);
+ }
+ mVotesStorage.updateVote(
+ displayId, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE, refreshRateVote);
+ mVotesStorage.updateVote(
+ displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, frameRateVote);
}
private void removeUserSettingDisplayPreferredSize(int displayId) {
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 88ee04481..459f9a6 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -110,37 +110,40 @@
int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13;
// For concurrent displays we want to limit refresh rate on all displays
- int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 14;
+ int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 14;
+
+ // For concurrent displays we want to limit refresh rate on all displays
+ int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 15;
// For internal application to limit display modes to specific ids
- int PRIORITY_SYSTEM_REQUESTED_MODES = 15;
+ int PRIORITY_SYSTEM_REQUESTED_MODES = 16;
// PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if
// Settings.Global.LOW_POWER_MODE is on.
// Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other
// higher priority votes), render rate limit can still apply
- int PRIORITY_LOW_POWER_MODE_MODES = 16;
+ int PRIORITY_LOW_POWER_MODE_MODES = 17;
// PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if
// Settings.Global.LOW_POWER_MODE is on.
- int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 17;
+ int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 18;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
// It's used to avoid refresh rate switches in certain conditions which may result in the
// user seeing the display flickering when the switches occur.
- int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 18;
+ int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 19;
// Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
- int PRIORITY_SKIN_TEMPERATURE = 19;
+ int PRIORITY_SKIN_TEMPERATURE = 20;
// The proximity sensor needs the refresh rate to be locked in order to function, so this is
// set to a high priority.
- int PRIORITY_PROXIMITY = 20;
+ int PRIORITY_PROXIMITY = 21;
// The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
// to function, so this needs to be the highest priority of all votes.
- int PRIORITY_UDFPS = 21;
+ int PRIORITY_UDFPS = 22;
@IntDef(prefix = { "PRIORITY_" }, value = {
PRIORITY_DEFAULT_RENDER_FRAME_RATE,
@@ -157,6 +160,7 @@
PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE,
PRIORITY_LIMIT_MODE,
PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE,
+ PRIORITY_LAYOUT_LIMITED_REFRESH_RATE,
PRIORITY_LAYOUT_LIMITED_FRAME_RATE,
PRIORITY_SYSTEM_REQUESTED_MODES,
PRIORITY_LOW_POWER_MODE_MODES,
@@ -283,6 +287,8 @@
return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
+ case PRIORITY_LAYOUT_LIMITED_REFRESH_RATE:
+ return "PRIORITY_LAYOUT_LIMITED_REFRESH_RATE";
case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
case PRIORITY_SYSTEM_REQUESTED_MODES:
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7ce9ee6..2ad0d2a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -326,7 +326,7 @@
* Figures out the target IME user ID for a given {@link Binder} IPC.
*
* @param callingProcessUserId the user ID of the calling process
- * @return User ID to be used for this {@link Binder} call.
+ * @return the user ID to be used for this {@link Binder} call
*/
@GuardedBy("ImfLock.class")
@UserIdInt
@@ -336,6 +336,30 @@
}
/**
+ * Figures out the targetIMuser for a given {@link Binder} IPC. In case
+ * {@code callingProcessUserId} is SYSTEM user, then it will return the owner of the display
+ * associated with the {@code client} passed as parameter.
+ *
+ * @param callingProcessUserId the user ID of the calling process
+ * @param client the input method client used to retrieve the user id in case
+ * {@code callingProcessUserId} is assigned to SYSTEM user
+ * @return the user ID to be used for this {@link Binder} call
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ @BinderThread
+ private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId,
+ @NonNull IInputMethodClient client) {
+ if (mConcurrentMultiUserModeEnabled
+ && callingProcessUserId == UserHandle.USER_SYSTEM) {
+ final var clientState = mClientController.getClient(client.asBinder());
+ return mUserManagerInternal.getUserAssignedToDisplay(
+ clientState.mSelfReportedDisplayId);
+ }
+ return callingProcessUserId;
+ }
+
+ /**
* Figures out the target IME user ID associated with the given {@code displayId}.
*
* @param displayId the display ID to be queried about
@@ -3069,7 +3093,7 @@
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(uid);
- final int userId = resolveImeUserIdLocked(callingUserId);
+ final int userId = resolveImeUserIdLocked(callingUserId, client);
final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags,
lastClickToolType, resultReceiver, reason, uid, userId);
// When ZeroJankProxy is enabled, the app has already received "true" as the return
@@ -3515,7 +3539,7 @@
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(uid);
- final int userId = resolveImeUserIdLocked(callingUserId);
+ final int userId = resolveImeUserIdLocked(callingUserId, client);
final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags,
resultReceiver, reason, uid, userId);
// When ZeroJankProxy is enabled, the app has already received "true" as the return
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 008746c..4fa7112 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -122,8 +122,9 @@
getNotificationShadeSections();
private static List<NotificationSectioner> getNotificationShadeSections() {
+ ArrayList<NotificationSectioner> sectionsList = new ArrayList<>();
if (android.service.notification.Flags.notificationClassification()) {
- return List.of(
+ sectionsList.addAll(List.of(
new NotificationSectioner("PromotionsSection", 0, (record) ->
NotificationChannel.PROMOTIONS_ID.equals(record.getChannel().getId())),
new NotificationSectioner("SocialSection", 0, (record) ->
@@ -131,18 +132,36 @@
new NotificationSectioner("NewsSection", 0, (record) ->
NotificationChannel.NEWS_ID.equals(record.getChannel().getId())),
new NotificationSectioner("RecsSection", 0, (record) ->
- NotificationChannel.RECS_ID.equals(record.getChannel().getId())),
- new NotificationSectioner("AlertingSection", 0, (record) ->
- record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
- new NotificationSectioner("SilentSection", 1, (record) ->
- record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
- } else {
- return List.of(
- new NotificationSectioner("AlertingSection", 0, (record) ->
- record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
- new NotificationSectioner("SilentSection", 1, (record) ->
- record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+ NotificationChannel.RECS_ID.equals(record.getChannel().getId()))));
}
+
+ if (Flags.notificationForceGroupConversations()) {
+ // add priority people section
+ sectionsList.add(new NotificationSectioner("PeopleSection(priority)", 1, (record) ->
+ record.isConversation() && record.getChannel().isImportantConversation()));
+
+ if (android.app.Flags.sortSectionByTime()) {
+ // add single people (alerting) section
+ sectionsList.add(new NotificationSectioner("PeopleSection", 0,
+ NotificationRecord::isConversation));
+ } else {
+ // add people alerting section
+ sectionsList.add(new NotificationSectioner("PeopleSection(alerting)", 1, (record) ->
+ record.isConversation()
+ && record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT));
+ // add people silent section
+ sectionsList.add(new NotificationSectioner("PeopleSection(silent)", 1, (record) ->
+ record.isConversation()
+ && record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+ }
+ }
+
+ sectionsList.addAll(List.of(
+ new NotificationSectioner("AlertingSection", 0, (record) ->
+ record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
+ new NotificationSectioner("SilentSection", 1, (record) ->
+ record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT)));
+ return sectionsList;
}
public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
@@ -830,61 +849,19 @@
}
}
+ // The list of notification operations required after the channel update
final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
- final Set<FullyQualifiedGroupKey> oldGroups =
- new HashSet<>(mAggregatedNotifications.keySet());
- for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
- // Only check aggregate groups that match the same userId & packageName
- if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
- final ArrayMap<String, NotificationAttributes> notificationsInAggGroup =
- mAggregatedNotifications.get(oldFullAggKey);
- if (notificationsInAggGroup == null) {
- continue;
- }
+ // Check any already auto-grouped notifications that may need to be re-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getAutogroupedNotificationsMoveOps(userId, pkgName,
+ notificationsToCheck));
- FullyQualifiedGroupKey newFullAggregateGroupKey = null;
- for (String key : notificationsInAggGroup.keySet()) {
- if (notificationsToCheck.get(key) != null) {
- // check if section changes
- NotificationSectioner sectioner = getSection(
- notificationsToCheck.get(key));
- if (sectioner == null) {
- continue;
- }
- newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
- sectioner);
- if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
- if (DEBUG) {
- Log.i(TAG, "Change section on channel update: " + key);
- }
- notificationsToMove.add(
- new NotificationMoveOp(notificationsToCheck.get(key),
- oldFullAggKey, newFullAggregateGroupKey));
- }
- }
- }
-
- if (newFullAggregateGroupKey != null) {
- // Add any notifications left ungrouped to the new section
- ArrayMap<String, NotificationAttributes> ungrouped =
- mUngroupedAbuseNotifications.get(newFullAggregateGroupKey);
- if (ungrouped != null) {
- for (NotificationRecord r : notificationList) {
- if (ungrouped.containsKey(r.getKey())) {
- if (DEBUG) {
- Log.i(TAG, "Add previously ungrouped: " + r);
- }
- notificationsToMove.add(
- new NotificationMoveOp(r, null, newFullAggregateGroupKey));
- }
- }
- //Cleanup mUngroupedAbuseNotifications
- mUngroupedAbuseNotifications.remove(newFullAggregateGroupKey);
- }
- }
- }
- }
+ // Check any ungrouped notifications that may need to be auto-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
// Batch move to new section
if (!notificationsToMove.isEmpty()) {
@@ -894,10 +871,103 @@
}
@GuardedBy("mAggregatedNotifications")
+ private List<NotificationMoveOp> getAutogroupedNotificationsMoveOps(int userId, String pkgName,
+ ArrayMap<String, NotificationRecord> notificationsToCheck) {
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ final Set<FullyQualifiedGroupKey> oldGroups =
+ new HashSet<>(mAggregatedNotifications.keySet());
+ // Move auto-grouped updated notifications from the old groups to the new groups (section)
+ for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
+ // Only check aggregate groups that match the same userId & packageName
+ if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
+ final ArrayMap<String, NotificationAttributes> notificationsInAggGroup =
+ mAggregatedNotifications.get(oldFullAggKey);
+ if (notificationsInAggGroup == null) {
+ continue;
+ }
+
+ FullyQualifiedGroupKey newFullAggregateGroupKey = null;
+ for (String key : notificationsInAggGroup.keySet()) {
+ if (notificationsToCheck.get(key) != null) {
+ // check if section changes
+ NotificationSectioner sectioner = getSection(notificationsToCheck.get(key));
+ if (sectioner == null) {
+ continue;
+ }
+ newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
+ sectioner);
+ if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
+ if (DEBUG) {
+ Log.i(TAG, "Change section on channel update: " + key);
+ }
+ notificationsToMove.add(
+ new NotificationMoveOp(notificationsToCheck.get(key),
+ oldFullAggKey, newFullAggregateGroupKey));
+ notificationsToCheck.remove(key);
+ }
+ }
+ }
+ }
+ }
+ return notificationsToMove;
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private List<NotificationMoveOp> getUngroupedNotificationsMoveOps(int userId, String pkgName,
+ final ArrayMap<String, NotificationRecord> notificationsToCheck) {
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ // Move any remaining ungrouped updated notifications from the old ungrouped list
+ // to the new ungrouped section list, if necessary
+ if (!notificationsToCheck.isEmpty()) {
+ final Set<FullyQualifiedGroupKey> oldUngroupedSectionKeys =
+ new HashSet<>(mUngroupedAbuseNotifications.keySet());
+ for (FullyQualifiedGroupKey oldFullAggKey : oldUngroupedSectionKeys) {
+ // Only check aggregate groups that match the same userId & packageName
+ if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
+ final ArrayMap<String, NotificationAttributes> ungroupedOld =
+ mUngroupedAbuseNotifications.get(oldFullAggKey);
+ if (ungroupedOld == null) {
+ continue;
+ }
+
+ FullyQualifiedGroupKey newFullAggregateGroupKey = null;
+ final Set<String> ungroupedKeys = new HashSet<>(ungroupedOld.keySet());
+ for (String key : ungroupedKeys) {
+ NotificationRecord record = notificationsToCheck.get(key);
+ if (record != null) {
+ // check if section changes
+ NotificationSectioner sectioner = getSection(record);
+ if (sectioner == null) {
+ continue;
+ }
+ newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
+ sectioner);
+ if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
+ if (DEBUG) {
+ Log.i(TAG, "Change ungrouped section: " + key);
+ }
+ notificationsToMove.add(
+ new NotificationMoveOp(record, oldFullAggKey,
+ newFullAggregateGroupKey));
+ notificationsToCheck.remove(key);
+ //Remove from previous ungrouped list
+ ungroupedOld.remove(key);
+ }
+ }
+ }
+ mUngroupedAbuseNotifications.put(oldFullAggKey, ungroupedOld);
+ }
+ }
+ }
+ return notificationsToMove;
+ }
+
+ @GuardedBy("mAggregatedNotifications")
private void moveNotificationsToNewSection(final int userId, final String pkgName,
final List<NotificationMoveOp> notificationsToMove) {
record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
boolean hasSummary) { }
+ // Bundled operations to apply to groups affected by the channel update
ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>();
for (NotificationMoveOp moveOp: notificationsToMove) {
@@ -923,35 +993,36 @@
// Only add once, for triggering notification
if (!groupsToUpdate.containsKey(oldFullAggregateGroupKey)) {
groupsToUpdate.put(oldFullAggregateGroupKey,
- new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
+ new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
}
}
- // Add/update aggregate summary for new group
+ // Add moved notifications to the ungrouped list for new group and do grouping
+ // after all notifications have been handled
if (newFullAggregateGroupKey != null) {
final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
mAggregatedNotifications.getOrDefault(newFullAggregateGroupKey,
new ArrayMap<>());
- boolean newGroupExists = !newAggregatedNotificationsAttrs.isEmpty();
- newAggregatedNotificationsAttrs.put(record.getKey(),
- new NotificationAttributes(record.getFlags(),
- record.getNotification().getSmallIcon(),
- record.getNotification().color,
- record.getNotification().visibility,
- record.getNotification().getGroupAlertBehavior(),
- record.getChannel().getId()));
- mAggregatedNotifications.put(newFullAggregateGroupKey,
- newAggregatedNotificationsAttrs);
+ boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(newFullAggregateGroupKey,
+ new ArrayMap<>());
+ ungrouped.put(record.getKey(), new NotificationAttributes(
+ record.getFlags(),
+ record.getNotification().getSmallIcon(),
+ record.getNotification().color,
+ record.getNotification().visibility,
+ record.getNotification().getGroupAlertBehavior(),
+ record.getChannel().getId()));
+ mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
+
+ record.setOverrideGroupKey(null);
// Only add once, for triggering notification
if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
groupsToUpdate.put(newFullAggregateGroupKey,
- new GroupUpdateOp(newFullAggregateGroupKey, record, newGroupExists));
+ new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
}
-
- // Add notification to new group. do not request resort
- record.setOverrideGroupKey(null);
- mCallback.addAutoGroup(record.getKey(), newFullAggregateGroupKey.toString(), false);
}
}
@@ -959,18 +1030,26 @@
for (FullyQualifiedGroupKey groupKey : groupsToUpdate.keySet()) {
final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
mAggregatedNotifications.getOrDefault(groupKey, new ArrayMap<>());
- if (aggregatedNotificationsAttrs.isEmpty()) {
- mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
- mAggregatedNotifications.remove(groupKey);
- } else {
- NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
- boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
+ final ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(groupKey, new ArrayMap<>());
+
+ NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
+ boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
+ //Group needs to be created/updated
+ if (ungrouped.size() >= mAutoGroupAtCount
+ || (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) {
NotificationSectioner sectioner = getSection(triggeringNotification);
if (sectioner == null) {
continue;
}
- updateAggregateAppGroup(groupKey, triggeringNotification.getKey(), hasSummary,
- sectioner.mSummaryId);
+ aggregateUngroupedNotifications(groupKey, triggeringNotification.getKey(),
+ ungrouped, hasSummary, sectioner.mSummaryId);
+ } else {
+ // Remove empty groups
+ if (aggregatedNotificationsAttrs.isEmpty() && hasSummary) {
+ mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
+ mAggregatedNotifications.remove(groupKey);
+ }
}
}
}
@@ -1327,8 +1406,10 @@
}
private boolean isNotificationGroupable(final NotificationRecord record) {
- if (record.isConversation()) {
- return false;
+ if (!Flags.notificationForceGroupConversations()) {
+ if (record.isConversation()) {
+ return false;
+ }
}
Notification notification = record.getSbn().getNotification();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 06d1285..6ff0e04 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -765,7 +765,9 @@
try {
ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
rule.name = applicationInfo.loadLabel(mPm).toString();
- rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
+ if (!Flags.modesUi()) {
+ rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
+ }
} catch (PackageManager.NameNotFoundException e) {
// Should not happen, since it's the app calling us (?)
Log.w(TAG, "Package not found for creating implicit zen rule");
@@ -1750,6 +1752,15 @@
manualRulePolicy.overwrittenWith(automaticRule.zenPolicy);
}
}
+
+ if (Flags.modesApi() && Flags.modesUi()
+ && config.version < ZenModeConfig.XML_VERSION_MODES_UI) {
+ // Clear icons from implicit rules. App icons are not suitable for some
+ // surfaces, so juse use a default (the user can select a different one).
+ if (ZenModeConfig.isImplicitRuleId(automaticRule.id)) {
+ automaticRule.iconResName = null;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 7265cff..aac2c40 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -149,3 +149,10 @@
description: "This flag enables forced auto-grouping singleton groups"
bug: "336488844"
}
+
+flag {
+ name: "notification_force_group_conversations"
+ namespace: "systemui"
+ description: "This flag enables forced auto-grouping conversations"
+ bug: "336488844"
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index d04b733..b45651d 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1167,7 +1167,6 @@
private static final int USB_DATA_CONNECTED = 2;
int mUsbDataState = USB_DATA_UNKNOWN;
- private static final int GPS_SIGNAL_QUALITY_NONE = 2;
int mGpsSignalQualityBin = -1;
final StopwatchTimer[] mGpsSignalQualityTimer =
new StopwatchTimer[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
@@ -5528,7 +5527,7 @@
mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
HistoryItem.STATE_GPS_ON_FLAG, uid, "gnss");
mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs,
- GPS_SIGNAL_QUALITY_NONE);
+ HistoryItem.GNSS_SIGNAL_QUALITY_NONE);
stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
mGpsSignalQualityBin = -1;
if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
index 393fa39..03df46a 100644
--- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
@@ -115,6 +115,12 @@
mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
}
} else {
+ if (mInitiatingUid == Process.INVALID_UID) {
+ if (item.eventCode == (BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH)) {
+ mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
+ }
+ }
recordUsageDuration(mPowerStats, mInitiatingUid, item.time);
mInitiatingUid = Process.INVALID_UID;
if (!mEnergyConsumerSupported) {
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
index 572bde9..0b28710 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
@@ -27,15 +27,15 @@
import java.util.Arrays;
public class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
- private int mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
- private long mGnssSignalLevelTimestamp;
- private final long[] mGnssSignalDurations =
- new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
private static final GnssPowerStatsLayout sStatsLayout = new GnssPowerStatsLayout();
private final UsageBasedPowerEstimator[] mSignalLevelEstimators =
new UsageBasedPowerEstimator[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
private final boolean mUseSignalLevelEstimators;
private long[] mTmpDeviceStatsArray;
+ private int mGnssSignalLevel;
+ private long mGnssSignalLevelTimestamp;
+ private final long[] mGnssSignalDurations =
+ new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
public GnssPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
super(BatteryConsumer.POWER_COMPONENT_GNSS, uidResolver,
@@ -55,20 +55,33 @@
}
@Override
- protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
- if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) == 0) {
- mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
- return STATE_OFF;
- }
+ void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ super.start(stats, timestampMs);
- noteGnssSignalLevel(item);
- return STATE_ON;
+ mGnssSignalLevelTimestamp = timestampMs;
+ mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+ Arrays.fill(mGnssSignalDurations, 0);
}
- private void noteGnssSignalLevel(BatteryStats.HistoryItem item) {
- int signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
- >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
- if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+ @Override
+ protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
+ return (item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) != 0
+ ? STATE_ON : STATE_OFF;
+ }
+
+ @Override
+ void noteStateChange(PowerComponentAggregatedPowerStats stats, BatteryStats.HistoryItem item) {
+ super.noteStateChange(stats, item);
+
+ int signalLevel;
+ if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) != 0) {
+ signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+ >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+ if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+ // Default GNSS signal quality to GOOD for the purposes of power attribution
+ signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD;
+ }
+ } else {
signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
}
if (signalLevel == mGnssSignalLevel) {
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 65fc7b2..d10ef31 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -28,7 +29,10 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import android.view.InputDevice;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
@@ -42,6 +46,7 @@
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
+import java.util.Locale;
/**
* Class that loads custom {@link VibrationEffect} to be performed for each
@@ -92,105 +97,146 @@
private static final String ATTRIBUTE_ID = "id";
/**
- * Parses the haptic feedback vibration customization XML file for the device, and provides a
- * mapping of the customized effect IDs to their respective {@link VibrationEffect}s.
- *
- * <p>This is potentially expensive, so avoid calling repeatedly. One call is enough, and the
- * caller should process the returned mapping (if any) for further queries.
- *
- * @param res {@link Resources} object to be used for reading the device's resources.
- * @return a {@link SparseArray} that maps each customized haptic feedback effect ID to its
- * respective {@link VibrationEffect}, or {@code null}, if the device has not configured
- * a file for haptic feedback constants customization.
- * @throws {@link IOException} if an IO error occurs while parsing the customization XML.
- * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing
- * the XML, like an invalid XML content or an invalid haptic feedback constant.
+ * A {@link SparseArray} that maps each customized haptic feedback effect ID to its
+ * respective {@link VibrationEffect}. If this is empty, system's default vibration will be
+ * used.
*/
- @Nullable
- static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo)
- throws CustomizationParserException, IOException {
- try {
- return loadVibrationsInternal(res, vibratorInfo);
- } catch (VibrationXmlParser.ParseFailedException
- | XmlParserException
- | XmlPullParserException e) {
- throw new CustomizationParserException(
- "Error parsing haptic feedback customization file.", e);
+ @NonNull
+ private final SparseArray<VibrationEffect> mHapticCustomizations;
+
+ /**
+ * A {@link SparseArray} similar to {@link mHapticCustomizations} but for rotary input source
+ * specific customization.
+ */
+ @NonNull
+ private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceRotary;
+
+ /**
+ * A {@link SparseArray} similar to {@link mHapticCustomizations} but for touch screen input
+ * source specific customization.
+ */
+ @NonNull
+ private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceTouchScreen;
+
+ HapticFeedbackCustomization(Resources res, VibratorInfo vibratorInfo) {
+ if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
+ Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
+ mHapticCustomizations = new SparseArray<>();
+ mHapticCustomizationsForSourceRotary = new SparseArray<>();
+ mHapticCustomizationsForSourceTouchScreen = new SparseArray<>();
+ return;
+ }
+
+ // Load base customizations.
+ SparseArray<VibrationEffect> hapticCustomizations;
+ hapticCustomizations = loadCustomizedFeedbackVibrationFromFile(res, vibratorInfo);
+ if (hapticCustomizations.size() == 0) {
+ // Input source customized haptic feedback was directly added in res. So, no need to old
+ // loading path.
+ hapticCustomizations = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+ R.xml.haptic_feedback_customization);
+ }
+ mHapticCustomizations = hapticCustomizations;
+
+ // Load customizations specified by input sources.
+ if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+ mHapticCustomizationsForSourceRotary =
+ loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+ R.xml.haptic_feedback_customization_source_rotary_encoder);
+ mHapticCustomizationsForSourceTouchScreen =
+ loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+ R.xml.haptic_feedback_customization_source_touchscreen);
+ } else {
+ mHapticCustomizationsForSourceRotary = new SparseArray<>();
+ mHapticCustomizationsForSourceTouchScreen = new SparseArray<>();
}
}
+ @VisibleForTesting
+ HapticFeedbackCustomization(@NonNull SparseArray<VibrationEffect> hapticCustomizations,
+ @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceRotary,
+ @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceTouchScreen) {
+ mHapticCustomizations = hapticCustomizations;
+ mHapticCustomizationsForSourceRotary = hapticCustomizationsForSourceRotary;
+ mHapticCustomizationsForSourceTouchScreen = hapticCustomizationsForSourceTouchScreen;
+ }
+
@Nullable
- private static SparseArray<VibrationEffect> loadVibrationsInternal(
- Resources res, VibratorInfo vibratorInfo) throws
- CustomizationParserException,
- IOException,
- XmlParserException,
- XmlPullParserException {
- if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
- Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
- return null;
+ VibrationEffect getEffect(int effectId) {
+ return mHapticCustomizations.get(effectId);
+ }
+
+ @Nullable
+ VibrationEffect getEffect(int effectId, int inputSource) {
+ VibrationEffect resultVibration = null;
+ if ((InputDevice.SOURCE_ROTARY_ENCODER & inputSource) != 0) {
+ resultVibration = mHapticCustomizationsForSourceRotary.get(effectId);
+ } else if ((InputDevice.SOURCE_TOUCHSCREEN & inputSource) != 0) {
+ resultVibration = mHapticCustomizationsForSourceTouchScreen.get(effectId);
}
-
- // Old loading path that reads customization from file at dir defined by config.
- TypedXmlPullParser parser = readCustomizationFile(res);
- if (parser == null) {
- // When old loading path doesn't succeed, try loading customization from resources.
- parser = readCustomizationResources(res);
+ if (resultVibration == null) {
+ resultVibration = mHapticCustomizations.get(effectId);
}
- if (parser == null) {
- Slog.d(TAG, "No loadable haptic feedback customization.");
- return null;
- }
+ return resultVibration;
+ }
- XmlUtils.beginDocument(parser, TAG_CONSTANTS);
- XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
- int rootDepth = parser.getDepth();
-
- SparseArray<VibrationEffect> mapping = new SparseArray<>();
- while (XmlReader.readNextTagWithin(parser, rootDepth)) {
- XmlValidator.checkStartTag(parser, TAG_CONSTANT);
- int customizationDepth = parser.getDepth();
-
- // Only attribute in tag is the `id` attribute.
- XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID);
- int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID);
- if (mapping.contains(effectId)) {
- throw new CustomizationParserException(
- "Multiple customizations found for effect " + effectId);
+ /**
+ * Parses the haptic feedback vibration customization XML file for the device whose directory is
+ * specified by config. See {@link R.string.config_hapticFeedbackCustomizationFile}.
+ *
+ * @return Return a mapping of the customized effect IDs to their respective
+ * {@link VibrationEffect}s.
+ */
+ @NonNull
+ private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromFile(
+ Resources res, VibratorInfo vibratorInfo) {
+ try {
+ TypedXmlPullParser parser = readCustomizationFile(res);
+ if (parser == null) {
+ Slog.d(TAG, "No loadable haptic feedback customization from file.");
+ return new SparseArray<>();
}
-
- // Move the parser one step into the `<constant>` tag.
- XmlValidator.checkParserCondition(
- XmlReader.readNextTagWithin(parser, customizationDepth),
- "Unsupported empty customization tag for effect " + effectId);
-
- ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
- parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
- VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
- if (effect != null) {
- if (effect.getDuration() == Long.MAX_VALUE) {
- throw new CustomizationParserException(String.format(
- "Vibration for effect ID %d is repeating, which is not allowed as a"
- + " haptic feedback: %s", effectId, effect));
- }
- mapping.put(effectId, effect);
- }
-
- XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
+ return parseVibrations(parser, vibratorInfo);
+ } catch (XmlPullParserException | XmlParserException | IOException e) {
+ Slog.e(TAG, "Error parsing haptic feedback customizations from file", e);
+ return new SparseArray<>();
}
+ }
- // Make checks that the XML ends well.
- XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth);
- XmlReader.readDocumentEndTag(parser);
-
- return mapping;
+ /**
+ * Parses the haptic feedback vibration customization XML resource for the device.
+ *
+ * @return Return a mapping of the customized effect IDs to their respective
+ * {@link VibrationEffect}s.
+ */
+ @NonNull
+ private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromRes(
+ Resources res, VibratorInfo vibratorInfo, int xmlResId) {
+ try {
+ TypedXmlPullParser parser = readCustomizationResources(res, xmlResId);
+ if (parser == null) {
+ Slog.d(TAG, "No loadable haptic feedback customization from res.");
+ return new SparseArray<>();
+ }
+ return parseVibrations(parser, vibratorInfo);
+ } catch (XmlPullParserException | XmlParserException | IOException e) {
+ Slog.e(TAG, "Error parsing haptic feedback customizations from res", e);
+ return new SparseArray<>();
+ }
}
// TODO(b/356412421): deprecate old path related files.
private static TypedXmlPullParser readCustomizationFile(Resources res)
throws XmlPullParserException {
- String customizationFile = res.getString(
- com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
+ String customizationFile;
+ try {
+ customizationFile = res.getString(
+ R.string.config_hapticFeedbackCustomizationFile);
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Customization file directory config not found.", e);
+ return null;
+ }
+
if (TextUtils.isEmpty(customizationFile)) {
return null;
}
@@ -211,13 +257,14 @@
return parser;
}
- private static TypedXmlPullParser readCustomizationResources(Resources res) {
+ @Nullable
+ private static TypedXmlPullParser readCustomizationResources(Resources res, int xmlResId) {
if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) {
return null;
}
final XmlResourceParser resParser;
try {
- resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization);
+ resParser = res.getXml(xmlResId);
} catch (Resources.NotFoundException e) {
Slog.e(TAG, "Haptic customization resource not found.", e);
return null;
@@ -226,16 +273,52 @@
return XmlUtils.makeTyped(resParser);
}
- /**
- * Represents an error while parsing a haptic feedback customization XML.
- */
- static final class CustomizationParserException extends Exception {
- private CustomizationParserException(String message) {
- super(message);
+ @NonNull
+ private static SparseArray<VibrationEffect> parseVibrations(TypedXmlPullParser parser,
+ VibratorInfo vibratorInfo)
+ throws XmlPullParserException, IOException, XmlParserException {
+ XmlUtils.beginDocument(parser, TAG_CONSTANTS);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+ int rootDepth = parser.getDepth();
+
+ SparseArray<VibrationEffect> mapping = new SparseArray<>();
+ while (XmlReader.readNextTagWithin(parser, rootDepth)) {
+ XmlValidator.checkStartTag(parser, TAG_CONSTANT);
+ int customizationDepth = parser.getDepth();
+
+ // Only attribute in tag is the `id` attribute.
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID);
+ int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID);
+ if (mapping.contains(effectId)) {
+ Slog.e(TAG, "Multiple customizations found for effect " + effectId);
+ return new SparseArray<>();
+ }
+
+ // Move the parser one step into the `<constant>` tag.
+ XmlValidator.checkParserCondition(
+ XmlReader.readNextTagWithin(parser, customizationDepth),
+ "Unsupported empty customization tag for effect " + effectId);
+
+ ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
+ parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+ VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
+ if (effect != null) {
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ Slog.e(TAG, String.format(Locale.getDefault(),
+ "Vibration for effect ID %d is repeating, which is not allowed as a"
+ + " haptic feedback: %s", effectId, effect));
+ return new SparseArray<>();
+ }
+ mapping.put(effectId, effect);
+ }
+
+ XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
}
- private CustomizationParserException(String message, Throwable cause) {
- super(message, cause);
- }
+ // Make checks that the XML ends well.
+ XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth);
+ XmlReader.readDocumentEndTag(parser);
+
+ return mapping;
}
}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 0761087..cae6b34 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -16,19 +16,17 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
-import android.util.Slog;
-import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import com.android.internal.annotations.VisibleForTesting;
-import java.io.IOException;
import java.io.PrintWriter;
/**
@@ -52,39 +50,29 @@
private final boolean mHapticTextHandleEnabled;
// Vibrator effect for haptic feedback during boot when safe mode is enabled.
private final VibrationEffect mSafeModeEnabledVibrationEffect;
- // Haptic feedback vibration customizations specific to the device.
- // If present and valid, a vibration here will be used for an effect.
- // Otherwise, the system's default vibration will be used.
- @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
+
+ private final HapticFeedbackCustomization mHapticFeedbackCustomization;
private float mKeyboardVibrationFixedAmplitude;
- public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
- this(res, vibrator.getInfo());
- }
-
public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
- this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
+ this(res, vibratorInfo, new HapticFeedbackCustomization(res, vibratorInfo));
}
- @VisibleForTesting HapticFeedbackVibrationProvider(
- Resources res,
- VibratorInfo vibratorInfo,
- @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
+ @VisibleForTesting
+ HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo,
+ HapticFeedbackCustomization hapticFeedbackCustomization) {
mVibratorInfo = vibratorInfo;
mHapticTextHandleEnabled = res.getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
+ mHapticFeedbackCustomization = hapticFeedbackCustomization;
- if (hapticCustomizations != null && hapticCustomizations.size() == 0) {
- hapticCustomizations = null;
- }
- mHapticCustomizations = hapticCustomizations;
- mSafeModeEnabledVibrationEffect =
- effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
- ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
- : VibrationSettings.createEffectFromResource(
- res,
- com.android.internal.R.array.config_safeModeEnabledVibePattern);
+ VibrationEffect safeModeVibration = mHapticFeedbackCustomization.getEffect(
+ HapticFeedbackConstants.SAFE_MODE_ENABLED);
+ mSafeModeEnabledVibrationEffect = safeModeVibration != null ? safeModeVibration
+ : VibrationSettings.createEffectFromResource(res,
+ com.android.internal.R.array.config_safeModeEnabledVibePattern);
+
mKeyboardVibrationFixedAmplitude = res.getFloat(
com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude);
if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) {
@@ -100,89 +88,41 @@
* @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
* the provided effect ID is not supported.
*/
- @Nullable public VibrationEffect getVibrationForHapticFeedback(int effectId) {
- switch (effectId) {
- case HapticFeedbackConstants.CONTEXT_CLICK:
- case HapticFeedbackConstants.GESTURE_END:
- case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
- case HapticFeedbackConstants.SCROLL_TICK:
- case HapticFeedbackConstants.SEGMENT_TICK:
- return getVibration(effectId, VibrationEffect.EFFECT_TICK);
-
- case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
- if (!mHapticTextHandleEnabled) {
- return null;
- }
- // fallthrough
- case HapticFeedbackConstants.CLOCK_TICK:
- case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
- return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.KEYBOARD_RELEASE:
- case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
- return getKeyboardVibration(effectId);
-
- case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
- case HapticFeedbackConstants.DRAG_CROSSING:
- return getVibration(
- effectId,
- VibrationEffect.EFFECT_TICK,
- /* fallbackForPredefinedEffect= */ false);
-
- case HapticFeedbackConstants.VIRTUAL_KEY:
- case HapticFeedbackConstants.EDGE_RELEASE:
- case HapticFeedbackConstants.CALENDAR_DATE:
- case HapticFeedbackConstants.CONFIRM:
- case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
- case HapticFeedbackConstants.GESTURE_START:
- case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
- case HapticFeedbackConstants.SCROLL_LIMIT:
- return getVibration(effectId, VibrationEffect.EFFECT_CLICK);
-
- case HapticFeedbackConstants.LONG_PRESS:
- case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
- case HapticFeedbackConstants.DRAG_START:
- case HapticFeedbackConstants.EDGE_SQUEEZE:
- return getVibration(effectId, VibrationEffect.EFFECT_HEAVY_CLICK);
-
- case HapticFeedbackConstants.REJECT:
- case HapticFeedbackConstants.BIOMETRIC_REJECT:
- return getVibration(effectId, VibrationEffect.EFFECT_DOUBLE_CLICK);
-
- case HapticFeedbackConstants.SAFE_MODE_ENABLED:
- return mSafeModeEnabledVibrationEffect;
-
- case HapticFeedbackConstants.ASSISTANT_BUTTON:
- return getAssistantButtonVibration();
-
- case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
- return getVibration(
- effectId,
- VibrationEffect.Composition.PRIMITIVE_TICK,
- /* primitiveScale= */ 0.4f,
- VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.TOGGLE_ON:
- return getVibration(
- effectId,
- VibrationEffect.Composition.PRIMITIVE_TICK,
- /* primitiveScale= */ 0.5f,
- VibrationEffect.EFFECT_TICK);
-
- case HapticFeedbackConstants.TOGGLE_OFF:
- return getVibration(
- effectId,
- VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
- /* primitiveScale= */ 0.2f,
- VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.NO_HAPTICS:
- default:
- return null;
+ @Nullable public VibrationEffect getVibration(int effectId) {
+ if (!isFeedbackConstantEnabled(effectId)) {
+ return null;
}
+ VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId);
+ if (customizedVibration != null) {
+ return customizedVibration;
+ }
+ return getVibrationForHapticFeedback(effectId);
}
/**
+ * Provides the {@link VibrationEffect} for a given haptic feedback effect ID (provided in
+ * {@link HapticFeedbackConstants}).
+ *
+ * @param effectId the haptic feedback effect ID whose respective vibration we want to get.
+ * @param inputSource the {@link InputDevice.Source} that customizes the haptic feedback
+ * corresponding to the {@code effectId}.
+ * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
+ * the provided effect ID is not supported.
+ */
+ @Nullable public VibrationEffect getVibration(int effectId, int inputSource) {
+ if (!isFeedbackConstantEnabled(effectId)) {
+ return null;
+ }
+ VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId,
+ inputSource);
+ if (customizedVibration != null) {
+ return customizedVibration;
+ }
+ return getVibrationForHapticFeedback(effectId);
+ }
+
+ // TODO(b/354049335): handle input source customized VibrationAttributes.
+ /**
* Provides the {@link VibrationAttributes} that should be used for a haptic feedback.
*
* @param effectId the haptic feedback effect ID whose respective vibration attributes we want
@@ -255,61 +195,106 @@
pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
}
- private VibrationEffect getVibration(int effectId, int predefinedVibrationEffectId) {
- return getVibration(
- effectId, predefinedVibrationEffectId, /* fallbackForPredefinedEffect= */ true);
+ private boolean isFeedbackConstantEnabled(int effectId) {
+ return switch (effectId) {
+ case HapticFeedbackConstants.TEXT_HANDLE_MOVE -> mHapticTextHandleEnabled;
+ case HapticFeedbackConstants.NO_HAPTICS -> false;
+ default -> true;
+ };
}
/**
- * Returns the customized vibration for {@code hapticFeedbackId}, or
- * {@code predefinedVibrationEffectId} if a customization does not exist for the haptic
- * feedback.
- *
- * <p>If a customization does not exist and the default predefined effect is to be returned,
- * {@code fallbackForPredefinedEffect} will be used to decide whether or not to fallback
- * to a generic pattern if the predefined effect is not hardware supported.
- *
- * @see VibrationEffect#get(int, boolean)
+ * Get {@link VibrationEffect} respective {@code effectId} from platform-wise mapping. This
+ * method doesn't include OEM customizations.
*/
- private VibrationEffect getVibration(
- int hapticFeedbackId,
- int predefinedVibrationEffectId,
- boolean fallbackForPredefinedEffect) {
- if (effectHasCustomization(hapticFeedbackId)) {
- return mHapticCustomizations.get(hapticFeedbackId);
+ @Nullable
+ private VibrationEffect getVibrationForHapticFeedback(int effectId) {
+ switch (effectId) {
+ case HapticFeedbackConstants.CONTEXT_CLICK:
+ case HapticFeedbackConstants.GESTURE_END:
+ case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
+ case HapticFeedbackConstants.SCROLL_TICK:
+ case HapticFeedbackConstants.SEGMENT_TICK:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+
+ case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
+ case HapticFeedbackConstants.CLOCK_TICK:
+ case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+ // keyboard effect is not customized by the input source.
+ return getKeyboardVibration(effectId);
+
+ case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
+ case HapticFeedbackConstants.DRAG_CROSSING:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK, /* fallback= */ false);
+
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ case HapticFeedbackConstants.EDGE_RELEASE:
+ case HapticFeedbackConstants.CALENDAR_DATE:
+ case HapticFeedbackConstants.CONFIRM:
+ case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
+ case HapticFeedbackConstants.GESTURE_START:
+ case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+ case HapticFeedbackConstants.SCROLL_LIMIT:
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ case HapticFeedbackConstants.LONG_PRESS:
+ case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+ case HapticFeedbackConstants.DRAG_START:
+ case HapticFeedbackConstants.EDGE_SQUEEZE:
+ return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+
+ case HapticFeedbackConstants.REJECT:
+ case HapticFeedbackConstants.BIOMETRIC_REJECT:
+ return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+
+ case HapticFeedbackConstants.SAFE_MODE_ENABLED:
+ // safe mode effect is not customized by the input source.
+ return mSafeModeEnabledVibrationEffect;
+
+ case HapticFeedbackConstants.ASSISTANT_BUTTON:
+ // assistant effect is not customized by the input source.
+ return getAssistantButtonVibration();
+
+ case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
+ return getVibration(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ /* primitiveScale= */ 0.4f,
+ VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.TOGGLE_ON:
+ return getVibration(
+ VibrationEffect.Composition.PRIMITIVE_TICK,/* primitiveScale= */ 0.5f,
+ VibrationEffect.EFFECT_TICK);
+
+ case HapticFeedbackConstants.TOGGLE_OFF:
+ return getVibration(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ /* primitiveScale= */ 0.2f,
+ VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.NO_HAPTICS:
+ default:
+ return null;
}
- return VibrationEffect.get(predefinedVibrationEffectId, fallbackForPredefinedEffect);
}
- /**
- * Returns the customized vibration for {@code hapticFeedbackId}, or some fallback vibration if
- * a customization does not exist for the ID.
- *
- * <p>The fallback will be a primitive composition formed of {@code primitiveId} and
- * {@code primitiveScale}, if the primitive is supported. Otherwise, it will be a predefined
- * vibration of {@code elsePredefinedVibrationEffectId}.
- */
- private VibrationEffect getVibration(
- int hapticFeedbackId,
- int primitiveId,
- float primitiveScale,
- int elsePredefinedVibrationEffectId) {
- if (effectHasCustomization(hapticFeedbackId)) {
- return mHapticCustomizations.get(hapticFeedbackId);
- }
+ @NonNull
+ private VibrationEffect getVibration(int primitiveId, float primitiveScale,
+ int predefinedVibrationEffectId) {
if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
return VibrationEffect.startComposition()
.addPrimitive(primitiveId, primitiveScale)
.compose();
- } else {
- return VibrationEffect.get(elsePredefinedVibrationEffectId);
}
+ return VibrationEffect.get(predefinedVibrationEffectId);
}
+ @NonNull
private VibrationEffect getAssistantButtonVibration() {
- if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
- return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
- }
if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
&& mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) {
// quiet ramp, short pause, then sharp tick
@@ -322,15 +307,8 @@
return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
}
- private boolean effectHasCustomization(int effectId) {
- return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
- }
-
+ @NonNull
private VibrationEffect getKeyboardVibration(int effectId) {
- if (effectHasCustomization(effectId)) {
- return mHapticCustomizations.get(effectId);
- }
-
int primitiveId;
int predefinedEffectId;
boolean predefinedEffectFallback;
@@ -354,8 +332,7 @@
.compose();
}
}
- return getVibration(effectId, predefinedEffectId,
- /* fallbackForPredefinedEffect= */ predefinedEffectFallback);
+ return VibrationEffect.get(predefinedEffectId, predefinedEffectFallback);
}
private VibrationAttributes createKeyboardVibrationAttributes(
@@ -367,17 +344,6 @@
return IME_FEEDBACK_VIBRATION_ATTRIBUTES;
}
- @Nullable
- private static SparseArray<VibrationEffect> loadHapticCustomizations(
- Resources res, VibratorInfo vibratorInfo) {
- try {
- return HapticFeedbackCustomization.loadVibrations(res, vibratorInfo);
- } catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
- Slog.e(TAG, "Unable to load haptic customizations.", e);
- return null;
- }
- }
-
private static boolean shouldBypassInterruptionPolicy(int effectId) {
switch (effectId) {
case HapticFeedbackConstants.SCROLL_TICK:
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index dd16d24..c143beb 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -487,39 +487,19 @@
HalVibration performHapticFeedbackInternal(
int uid, int deviceId, String opPkg, int constant, String reason,
IBinder token, int flags, int privFlags) {
-
// Make sure we report the constant id in the requested haptic feedback reason.
reason = "performHapticFeedback(constant=" + constant + "): " + reason;
-
HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
- if (hapticVibrationProvider == null) {
- Slog.e(TAG, "performHapticFeedback; haptic vibration provider not ready.");
- logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
- Vibration.Status.IGNORED_ERROR_SCHEDULING);
+ Vibration.Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason,
+ hapticVibrationProvider);
+ if (ignoreStatus != null) {
+ logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
return null;
}
-
- if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
- && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
- Slog.w(TAG, "performHapticFeedback; no permission for system constant " + constant);
- logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
- Vibration.Status.IGNORED_MISSING_PERMISSION);
- return null;
- }
-
- VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
- if (effect == null) {
- Slog.w(TAG, "performHapticFeedback; vibration absent for constant " + constant);
- logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
- Vibration.Status.IGNORED_UNSUPPORTED);
- return null;
- }
-
- CombinedVibration vib = CombinedVibration.createParallel(effect);
- VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
- constant, flags, privFlags);
- VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
- return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
+ return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
+ hapticVibrationProvider.getVibration(constant),
+ hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ constant, flags, privFlags));
}
/**
@@ -532,12 +512,35 @@
HalVibration performHapticFeedbackForInputDeviceInternal(
int uid, int deviceId, String opPkg, int constant, int inputDeviceId, int inputSource,
String reason, IBinder token, int flags, int privFlags) {
- // TODO(b/355543835): implement input device specific logic.
- if (DEBUG) {
- Slog.d(TAG, "performHapticFeedbackForInput: input device specific not implemented.");
+ // Make sure we report the constant id in the requested haptic feedback reason.
+ reason = "performHapticFeedbackForInputDevice(constant=" + constant + ", inputDeviceId="
+ + inputDeviceId + ", inputSource=" + inputSource + "): " + reason;
+ HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
+ Vibration.Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason,
+ hapticVibrationProvider);
+ if (ignoreStatus != null) {
+ logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
+ return null;
}
- return performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
- this, flags, privFlags);
+ return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
+ hapticVibrationProvider.getVibration(constant, inputSource),
+ hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ constant, flags, privFlags));
+ }
+
+ private HalVibration performHapticFeedbackWithEffect(int uid, int deviceId, String opPkg,
+ int constant, String reason, IBinder token, VibrationEffect effect,
+ VibrationAttributes attrs) {
+ if (effect == null) {
+ logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
+ Vibration.Status.IGNORED_UNSUPPORTED);
+ Slog.w(TAG,
+ "performHapticFeedbackWithEffect; vibration absent for constant " + constant);
+ return null;
+ }
+ CombinedVibration vib = CombinedVibration.createParallel(effect);
+ VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
+ return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
}
/**
@@ -1237,6 +1240,21 @@
return null;
}
+ @Nullable
+ private Vibration.Status shouldIgnoreHapticFeedback(int constant, String reason,
+ HapticFeedbackVibrationProvider hapticVibrationProvider) {
+ if (hapticVibrationProvider == null) {
+ Slog.e(TAG, reason + "; haptic vibration provider not ready.");
+ return Vibration.Status.IGNORED_ERROR_SCHEDULING;
+ }
+ if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
+ && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
+ Slog.w(TAG, reason + "; no permission for system constant " + constant);
+ return Vibration.Status.IGNORED_MISSING_PERMISSION;
+ }
+ return null;
+ }
+
/**
* Return true if the vibration has the same token and usage belongs to given usage class.
*
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2294d65..21908d9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -233,7 +233,6 @@
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
import static com.android.server.wm.TaskPersister.DEBUG;
@@ -2620,7 +2619,8 @@
|| mStartingWindow == null
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
// skip copy splash screen to client if it was resized
- || (mStartingData != null && mStartingData.mResizedFromTransfer)) {
+ || (mStartingData != null && mStartingData.mResizedFromTransfer)
+ || isRelaunching()) {
return false;
}
if (isTransferringSplashScreen()) {
@@ -5542,10 +5542,7 @@
return false;
}
if (!mDisplayContent.mAppTransition.isTransitionSet()) {
- // Defer committing visibility for non-home app which is animating by recents.
- if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
- return false;
- }
+ return false;
}
if (mWaitForEnteringPinnedMode && mVisible == visible) {
// If the visibility is not changed during enter PIP, we don't want to include it in
@@ -5699,8 +5696,7 @@
private void postApplyAnimation(boolean visible, boolean fromTransition) {
final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
- | ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
if (!delayed && !usingShellTransitions) {
// We aren't delayed anything, but exiting windows rely on the animation finished
// callback being called in case the ActivityRecord was pretending to be delayed,
@@ -5722,7 +5718,7 @@
// animation and aren't in RESUMED state. Otherwise, we'll update client visibility in
// onAnimationFinished or activityStopped.
if (visible || (mState != RESUMED && (usingShellTransitions || !isAnimating(
- PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)))) {
+ PARENTS, ANIMATION_TYPE_APP_TRANSITION)))) {
setClientVisible(visible);
}
@@ -7700,7 +7696,7 @@
// Ensure that the activity content is hidden when the decor surface is boosted to
// prevent UI redressing attack.
&& !isDecorSurfaceBoosted)
- || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 06bdc04..197bd5a 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,7 +68,6 @@
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -173,41 +172,6 @@
|| !transitionGoodToGoForTaskFragments()) {
return;
}
- final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
- ConfigurationContainer::isActivityTypeRecents);
- // In order to avoid visual clutter caused by a conflict between app transition
- // animation and recents animation, app transition is delayed until recents finishes.
- // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
- // task switcher (isRecentsInOpening=true), app transition must start even though
- // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
- // When 1P launcher is used, this animation is controlled by the launcher outside of
- // the app transition, so delaying app transition doesn't cause visible delay. After
- // recents finishes, app transition is handled just to commit visibility on apps.
- if (!isRecentsInOpening) {
- final ArraySet<WindowContainer> participants = new ArraySet<>();
- participants.addAll(mDisplayContent.mOpeningApps);
- participants.addAll(mDisplayContent.mChangingContainers);
- boolean deferForRecents = false;
- for (int i = 0; i < participants.size(); i++) {
- WindowContainer wc = participants.valueAt(i);
- final ActivityRecord activity = getAppFromContainer(wc);
- if (activity == null) {
- continue;
- }
- // Don't defer recents animation if one of activity isn't running for it, that one
- // might be started from quickstep.
- if (!activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
- deferForRecents = false;
- break;
- }
- deferForRecents = true;
- }
- if (deferForRecents) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for recents animation to finish");
- return;
- }
- }
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 87867f6..2cbd7f2 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1820,8 +1820,10 @@
mNavigationMonitor.cancelBackNavigating("cancelAnimation");
mBackAnimationAdapter.getRunner().onAnimationCancelled();
} else {
- mBackAnimationAdapter.getRunner().onAnimationStart(
- targets, null, null, callback);
+ mBackAnimationAdapter.getRunner().onAnimationStart(targets,
+ mOpenAnimAdaptor.mPreparedOpenTransition != null
+ ? mOpenAnimAdaptor.mPreparedOpenTransition.getToken()
+ : null, callback);
}
} catch (RemoteException e) {
e.printStackTrace();
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9cfd396..57f9be0 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -32,8 +32,8 @@
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -585,7 +585,6 @@
ANIMATION_TYPE_APP_TRANSITION,
ANIMATION_TYPE_SCREEN_ROTATION,
ANIMATION_TYPE_DIMMER,
- ANIMATION_TYPE_RECENTS,
ANIMATION_TYPE_WINDOW_ANIMATION,
ANIMATION_TYPE_INSETS_CONTROL,
ANIMATION_TYPE_TOKEN_TRANSFORM,
@@ -604,7 +603,6 @@
case ANIMATION_TYPE_APP_TRANSITION: return "app_transition";
case ANIMATION_TYPE_SCREEN_ROTATION: return "screen_rotation";
case ANIMATION_TYPE_DIMMER: return "dimmer";
- case ANIMATION_TYPE_RECENTS: return "recents_animation";
case ANIMATION_TYPE_WINDOW_ANIMATION: return "window_animation";
case ANIMATION_TYPE_INSETS_CONTROL: return "insets_animation";
case ANIMATION_TYPE_TOKEN_TRANSFORM: return "token_transform";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f000223..21be0fc 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -94,7 +94,6 @@
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.TaskProto.AFFINITY;
import static com.android.server.wm.TaskProto.BOUNDS;
import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
@@ -2966,8 +2965,7 @@
/** Checking if self or its child tasks are animated by recents animation. */
boolean isAnimatingByRecents() {
- return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
- || mTransitionController.isTransientHide(this);
+ return mTransitionController.isTransientHide(this);
}
WindowState getTopVisibleAppMainWindow() {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03342d3..13334a5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -19,7 +19,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -218,8 +217,8 @@
private void updateRunningExpensiveAnimationsLegacy() {
final boolean runningExpensiveAnimations =
mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
- | ANIMATION_TYPE_RECENTS /* typesToCheck */);
+ ANIMATION_TYPE_APP_TRANSITION
+ | ANIMATION_TYPE_SCREEN_ROTATION /* typesToCheck */);
if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
mService.mSnapshotController.setPause(true);
mTransaction.setEarlyWakeupStart();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a980b77..6995027 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -51,7 +51,6 @@
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -1242,8 +1241,7 @@
*/
boolean inTransitionSelfOrParent() {
if (!mTransitionController.isShellTransitionsEnabled()) {
- return isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+ return isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION);
}
return inTransition();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index ad88062..80f3c44 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -20,7 +20,7 @@
import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
@@ -118,7 +118,7 @@
mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
mWindowContainer.getDisplayContent().getWindowCornerRadius()),
mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION);
}
private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index eeda2a7..7493178 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -129,7 +129,6 @@
import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -2725,8 +2724,7 @@
win.mTransitionController.mAnimatingExitWindows.add(win);
reason = "inTransition";
}
- } else if (win.isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+ } else if (win.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
// Already animating as part of a legacy app-transition.
reason = "inLegacyTransition";
}
@@ -8951,7 +8949,7 @@
final ActivityRecord touchedApp = t.getActivityRecord();
if (touchedApp != null && touchedApp.getTask() != null) {
final ActivityRecord top = touchedApp.getTask().topRunningActivity();
- if (top != touchedApp && top.getTaskFragment().getBounds().contains(
+ if (top != null && top != touchedApp && top.getTaskFragment().getBounds().contains(
touchedApp.getTaskFragment().getBounds())) {
// This is a special case where the pointer-down-outside focus on an Activity that's
// entirely occluded by the task top running activity, this is possible if the
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81bce18..e5e153a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -128,7 +128,6 @@
import static com.android.server.wm.MoveAnimationSpecProto.TO;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -581,7 +580,7 @@
* is guaranteed to be cleared.
*/
static final int EXIT_ANIMATING_TYPES = ANIMATION_TYPE_APP_TRANSITION
- | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS;
+ | ANIMATION_TYPE_WINDOW_ANIMATION;
/** Currently running an exit animation? */
boolean mAnimatingExit;
@@ -4667,17 +4666,6 @@
if (!isImeLayeringTarget()) {
return false;
}
- if (!com.android.window.flags.Flags.doNotSkipImeByTargetVisibility()) {
- // Note that we don't process IME window if the IME input target is not on the screen.
- // In case some unexpected IME visibility cases happen like starting the remote
- // animation on the keyguard but seeing the IME window that originally on the app
- // which behinds the keyguard.
- final WindowState imeInputTarget = getImeInputTarget();
- if (imeInputTarget != null
- && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
- return false;
- }
- }
return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1290fb7..a80ee0f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2726,22 +2726,14 @@
return;
}
- if (Flags.securityLogV2Enabled()) {
- boolean auditLoggingEnabled = Boolean.TRUE.equals(
- mDevicePolicyEngine.getResolvedPolicy(
- PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
- boolean securityLoggingEnabled = Boolean.TRUE.equals(
- mDevicePolicyEngine.getResolvedPolicy(
- PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
- setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
- mInjector.runCryptoSelfTest();
- } else {
- synchronized (getLockObject()) {
- mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
- mInjector.runCryptoSelfTest();
- maybePauseDeviceWideLoggingLocked();
- }
- }
+ boolean auditLoggingEnabled = Boolean.TRUE.equals(
+ mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
+ boolean securityLoggingEnabled = Boolean.TRUE.equals(
+ mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
+ setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+ mInjector.runCryptoSelfTest();
}
/**
@@ -3399,7 +3391,7 @@
@GuardedBy("getLockObject()")
private void maybeMigrateSecurityLoggingPolicyLocked() {
- if (!Flags.securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
+ if (mOwners.isSecurityLoggingMigrated()) {
return;
}
@@ -16304,9 +16296,6 @@
@Override
public void enforceSecurityLoggingPolicy(boolean enabled) {
- if (!Flags.securityLogV2Enabled()) {
- return;
- }
Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL);
enforceLoggingPolicy(enabled, Boolean.TRUE.equals(auditLoggingEnabled));
@@ -16314,9 +16303,6 @@
@Override
public void enforceAuditLoggingPolicy(boolean enabled) {
- if (!Flags.securityLogV2Enabled()) {
- return;
- }
Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL);
enforceLoggingPolicy(Boolean.TRUE.equals(securityLoggingEnabled), enabled);
@@ -18252,45 +18238,20 @@
}
final CallerIdentity caller = getCallerIdentity(who, packageName);
- if (Flags.securityLogV2Enabled()) {
- EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- caller.getPackageName(),
- caller.getUserId());
- if (enabled) {
- mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.SECURITY_LOGGING,
- admin,
- new BooleanPolicyValue(true));
- } else {
- mDevicePolicyEngine.removeGlobalPolicy(
- PolicyDefinition.SECURITY_LOGGING,
- admin);
- }
+ EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+ who,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+ caller.getPackageName(),
+ caller.getUserId());
+ if (enabled) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.SECURITY_LOGGING,
+ admin,
+ new BooleanPolicyValue(true));
} else {
- synchronized (getLockObject()) {
- if (who != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
-
- if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
- return;
- }
- mInjector.securityLogSetLoggingEnabledProperty(enabled);
- if (enabled) {
- mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
- maybePauseDeviceWideLoggingLocked();
- } else {
- mSecurityLogMonitor.stop();
- }
- }
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.SECURITY_LOGGING,
+ admin);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURITY_LOGGING_ENABLED)
@@ -18312,29 +18273,14 @@
return mInjector.securityLogGetLoggingEnabledProperty();
}
- if (Flags.securityLogV2Enabled()) {
- final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- caller.getPackageName(),
- caller.getUserId());
- final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
- PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
- return Boolean.TRUE.equals(policy);
- } else {
- synchronized (getLockObject()) {
- if (admin != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
- return mInjector.securityLogGetLoggingEnabledProperty();
- }
- }
+ final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ admin,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+ caller.getPackageName(),
+ caller.getUserId());
+ final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
+ return Boolean.TRUE.equals(policy);
}
private void recordSecurityLogRetrievalTime() {
@@ -18410,42 +18356,24 @@
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- if (Flags.securityLogV2Enabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- caller.getPackageName(),
- caller.getUserId());
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ admin,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+ caller.getPackageName(),
+ caller.getUserId());
- synchronized (getLockObject()) {
- Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
- || areAllUsersAffiliatedWithDeviceLocked());
- }
-
- Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
- PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
-
- if (!Boolean.TRUE.equals(policy)) {
- Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs",
- caller.getPackageName());
- return null;
- }
- } else {
- if (admin != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
+ synchronized (getLockObject()) {
Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
|| areAllUsersAffiliatedWithDeviceLocked());
+ }
- if (!mInjector.securityLogGetLoggingEnabledProperty()) {
- return null;
- }
+ Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
+
+ if (!Boolean.TRUE.equals(policy)) {
+ Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs",
+ caller.getPackageName());
+ return null;
}
recordSecurityLogRetrievalTime();
@@ -18465,10 +18393,6 @@
}
final CallerIdentity caller = getCallerIdentity(callingPackage);
- if (!Flags.securityLogV2Enabled()) {
- throw new UnsupportedOperationException("Audit log not enabled");
- }
-
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
null /* admin */,
MANAGE_DEVICE_POLICY_AUDIT_LOGGING,
@@ -18493,10 +18417,6 @@
return false;
}
- if (!Flags.securityLogV2Enabled()) {
- throw new UnsupportedOperationException("Audit log not enabled");
- }
-
final CallerIdentity caller = getCallerIdentity(callingPackage);
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
null /* admin */,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 2ea5f16..52a7845 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -410,9 +410,8 @@
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
- if (Flags.securityLogV2Enabled()) {
- out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
- }
+ out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
+
if (Flags.unmanagedModeMigration()) {
out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED,
mRequiredPasswordComplexityMigrated);
@@ -483,8 +482,8 @@
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
null, ATTR_MIGRATED_POST_UPGRADE, false);
- mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
- && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+ mSecurityLoggingMigrated =
+ parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration()
&& parser.getAttributeBoolean(null,
ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index dd049303..474c48a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -23,7 +23,6 @@
import android.app.admin.IAuditLogEventsCallback;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
-import android.app.admin.flags.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
@@ -184,28 +183,6 @@
@GuardedBy("mLock")
private final ArrayDeque<SecurityEvent> mAuditLogEventBuffer = new ArrayDeque<>();
- /**
- * Start security logging.
- *
- * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all
- * users on the device.
- */
- void start(int enabledUser) {
- Slog.i(TAG, "Starting security logging for user " + enabledUser);
- mEnabledUser = enabledUser;
- mLock.lock();
- try {
- if (mMonitorThread == null) {
- resetLegacyBufferLocked();
- startMonitorThreadLocked();
- } else {
- Slog.i(TAG, "Security log monitor thread is already running");
- }
- } finally {
- mLock.unlock();
- }
- }
-
void stop() {
Slog.i(TAG, "Stopping security logging.");
mLock.lock();
@@ -467,11 +444,11 @@
assignLogId(event);
}
- if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (mLegacyLogEnabled) {
addToLegacyBufferLocked(dedupedLogs);
}
- if (Flags.securityLogV2Enabled() && mAuditLogEnabled) {
+ if (mAuditLogEnabled) {
addAuditLogEventsLocked(dedupedLogs);
}
}
@@ -548,7 +525,7 @@
saveLastEvents(newLogs);
newLogs.clear();
- if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (mLegacyLogEnabled) {
notifyDeviceOwnerOrProfileOwnerIfNeeded(force);
}
} catch (IOException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 09c54cb..37e7cfc 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -106,7 +106,7 @@
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.policy.AttributeCache;
-import com.android.internal.protolog.ProtoLogService;
+import com.android.internal.protolog.ProtoLogConfigurationService;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.FrameworkStatsLog;
@@ -1092,8 +1092,9 @@
// Orchestrates some ProtoLogging functionality.
if (android.tracing.Flags.clientSideProtoLogging()) {
- t.traceBegin("StartProtoLogService");
- ServiceManager.addService(Context.PROTOLOG_SERVICE, new ProtoLogService());
+ t.traceBegin("StartProtoLogConfigurationService");
+ ServiceManager.addService(
+ Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService());
t.traceEnd();
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 0787058..2c78504 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -33,11 +33,15 @@
import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.util.Log;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
@@ -56,6 +60,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,6 +94,9 @@
private String mInputMethodId;
private boolean mShowImeWithHardKeyboardEnabled;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -155,7 +163,13 @@
() -> assertThat(mUiDevice.pressHome()).isTrue(),
true /* expected */,
false /* inputViewStarted */);
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
@@ -182,8 +196,13 @@
/**
* This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
+ *
+ * With the refactor in b/298172246, all calls to IMMS#{show,hide}MySoftInputLocked
+ * will be just apply the requested visibility (by using the callback). Therefore, we will
+ * lose flags like HIDE_IMPLICIT_ONLY.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowHideSelf() throws Exception {
setShowImeWithHardKeyboard(true /* enabled */);
@@ -375,8 +394,13 @@
/**
* This checks that an implicit show request when the IME is not previously shown,
* and it should be shown in fullscreen mode, results in the IME not being shown.
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * SHOW_IMPLICIT.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
setShowImeWithHardKeyboard(true /* enabled */);
@@ -425,8 +449,13 @@
/**
* This checks that an implicit show request when a hard keyboard is connected,
* results in the IME not being shown.
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * SHOW_IMPLICIT.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception {
setShowImeWithHardKeyboard(false /* enabled */);
@@ -484,8 +513,13 @@
* This checks that an implicit show request followed by connecting a hard keyboard
* and a configuration change, does not trigger IMS#onFinishInputView,
* but results in the IME being hidden.
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * SHOW_IMPLICIT.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_thenConfigurationChanged() throws Exception {
setShowImeWithHardKeyboard(false /* enabled */);
@@ -567,8 +601,13 @@
* This checks that a forced show request directly followed by an explicit show request,
* and then a hide not always request, still results in the IME being shown
* (i.e. the explicit show request retains the forced state).
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * HIDE_NOT_ALWAYS.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways()
throws Exception {
setShowImeWithHardKeyboard(true /* enabled */);
@@ -734,7 +773,13 @@
backButtonUiObject.click();
mInstrumentation.waitForIdleSync();
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
@@ -766,7 +811,13 @@
backButtonUiObject.longClick();
mInstrumentation.waitForIdleSync();
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
@@ -848,7 +899,13 @@
assertWithMessage("Input Method Switcher Menu is shown")
.that(isInputMethodPickerShown(imm))
.isTrue();
- assertThat(mInputMethodService.isInputViewShown()).isTrue();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+ }
// Hide the Picker menu before finishing.
mUiDevice.pressBack();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index ab0f0c1..d91f154 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -3556,12 +3556,16 @@
new RefreshRateRange(refreshRate, refreshRate);
displayListener.onDisplayChanged(DISPLAY_ID);
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, refreshRate, refreshRate);
mInjector.mDisplayInfo.layoutLimitedRefreshRate = null;
displayListener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
+ assertNull(vote);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
assertNull(vote);
}
@@ -3585,6 +3589,8 @@
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
assertNull(vote);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
+ assertNull(vote);
}
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 0703db2..18811de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -223,6 +223,11 @@
mShutDownActionReceiver = receiver;
return null;
}
+
+ @Override
+ public int getUserId() {
+ return 0;
+ }
}
@Before
@@ -237,7 +242,7 @@
mPackageCategories = new HashMap<>();
mPackageUids = new HashMap<>();
mPackageName = mMockContext.getPackageName();
- mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME, -1);
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
@@ -245,7 +250,12 @@
}
private void mockAppCategory(String packageName, int packageUid,
- @ApplicationInfo.Category int category)
+ @ApplicationInfo.Category int category) throws Exception {
+ mockAppCategory(packageName, packageUid, category, -1 /*userId*/);
+ }
+
+ private void mockAppCategory(String packageName, int packageUid,
+ @ApplicationInfo.Category int category, int userId)
throws Exception {
reset(mMockPackageManager);
mPackageCategories.put(packageName, category);
@@ -259,8 +269,15 @@
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.packageName = packageName;
applicationInfo.category = category;
- when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
- .thenReturn(applicationInfo);
+ if (userId == -1) {
+ when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(),
+ anyInt()))
+ .thenReturn(applicationInfo);
+ } else {
+ when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(),
+ eq(userId)))
+ .thenReturn(applicationInfo);
+ }
final PackageInfo pi = new PackageInfo();
pi.packageName = packageName;
@@ -2331,10 +2348,12 @@
@Test
public void testGamePowerMode_twoGames() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String someGamePkg = "some.game";
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME,
+ ActivityManager.getCurrentUser());
HashMap<Integer, Boolean> powerState = new HashMap<>();
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
@@ -2354,10 +2373,12 @@
@Test
public void testGamePowerMode_twoGamesOverlap() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String someGamePkg = "some.game";
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME,
+ ActivityManager.getCurrentUser());
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2372,7 +2393,8 @@
@Test
public void testGamePowerMode_noPackage() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String[] packages = {};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2383,23 +2405,24 @@
@Test
public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
-
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ int userId = ActivityManager.getCurrentUser();
String nonGamePkg1 = "not.game1";
int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE, userId);
String nonGamePkg2 = "not.game2";
int nonGameUid2 = DEFAULT_PACKAGE_UID + 2;
- mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE, userId);
String gamePkg1 = "game1";
int gameUid1 = DEFAULT_PACKAGE_UID + 3;
- mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME, userId);
String gamePkg2 = "game2";
int gameUid2 = DEFAULT_PACKAGE_UID + 4;
- mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME, userId);
// non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2470,15 +2493,17 @@
@Test
public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception {
mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ int userId = ActivityManager.getCurrentUser();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String nonGamePkg1 = "not.game1";
int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE, userId);
String gamePkg1 = "game1";
int gameUid1 = DEFAULT_PACKAGE_UID + 3;
- mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME, userId);
// non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
index 127ab8a..f22279a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
@@ -74,6 +74,7 @@
private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
private static final int VOLTAGE_MV = 3500;
private static final int ENERGY_CONSUMER_ID = 777;
+ private static final long START_TIME = 10_000_000_000L;
private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
@Mock
@@ -113,11 +114,13 @@
};
private MonotonicClock mMonotonicClock;
+ private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+ mMonotonicClock = new MonotonicClock(START_TIME, mStatsRule.getMockClock());
+ mHistoryItem.clear();
}
@Test
@@ -129,7 +132,6 @@
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
() -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
- stats.start(0);
GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
collector.addConsumer(
@@ -142,9 +144,11 @@
stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
// Turn the screen off after 2.5 seconds
- stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+ START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ START_TIME + 5000);
stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
@@ -158,7 +162,87 @@
mStatsRule.setTime(11_000, 11_000);
collector.collectAndDeliverStats();
- stats.finish(11_000);
+ stats.finish(START_TIME + 11_000);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+ statsLayout.fromExtras(descriptor.extras);
+
+ // scr-on, GNSS-good: 2500 * 100 = 250000 mA-ms = 0.06944 mAh
+ // scr-off GNSS=good: 4500 * 100 = 0.12500 mAh
+ // scr-off GNSS=poor: 3000 * 1000 = 0.83333 mAh
+ // scr-off GNSS-on: 0.12500 + 0.83333 = 0.95833 mAh
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.06944);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.12500 + 0.83333);
+
+ // UID1 =
+ // scr-on FG: 2500 -> 0.06944 mAh
+ // scr-off BG: 2500/7500 * 0.95833 = 0.31944 mAh
+ // scr-off FGS: 1000/7500 * 0.95833 = 0.12777 mAh
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.06944);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.31944);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.12777);
+
+ // UID2 =
+ // scr-off cached: 4000/7500 * 0.95833 = 0.51111 mAh
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.51111);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0);
+ }
+
+ @Test
+ public void initialStateGnssOn() {
+ // ODPM unsupported
+ when(mConsumedEnergyRetriever
+ .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS), any()))
+ .thenReturn(new int[0]);
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
+ () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
+
+ stats.noteStateChange(buildHistoryItemInitialStateGpsOn(0));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+ START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ START_TIME + 5000);
+
+ stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
+
+ stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
+ stats.noteStateChange(buildHistoryItem(7000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+ stats.noteStateChange(buildHistoryItem(8000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+ mStatsRule.setTime(11_000, 11_000);
+
+ stats.finish(START_TIME + 11_000);
PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
@@ -224,8 +308,6 @@
powerStats -> stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime()));
collector.setEnabled(true);
- stats.start(0);
-
// Establish a baseline
when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID}))
.thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 10000));
@@ -234,9 +316,11 @@
stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
// Turn the screen off after 2.5 seconds
- stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+ START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ START_TIME + 5000);
stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
@@ -245,16 +329,14 @@
collector.collectAndDeliverStats();
stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
- stats.noteStateChange(buildHistoryItem(7000,
- GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
- stats.noteStateChange(buildHistoryItem(8000,
- GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+ stats.noteStateChange(buildHistoryItem(7000, GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+ stats.noteStateChange(buildHistoryItem(8000, GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
mStatsRule.setTime(11_000, 11_000);
when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID}))
.thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 3_610_000));
collector.collectAndDeliverStats();
- stats.finish(11_000);
+ stats.finish(START_TIME + 11_000);
PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
@@ -313,33 +395,45 @@
.isWithin(PRECISION).of(0);
}
- private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
- int uid) {
+ private BatteryStats.HistoryItem buildHistoryItemInitialStateGpsOn(long timestamp) {
mStatsRule.setTime(timestamp, timestamp);
- BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
- historyItem.time = mMonotonicClock.monotonicTime();
- historyItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
- if (stateOn) {
- historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
- | BatteryStats.HistoryItem.EVENT_FLAG_START;
- } else {
- historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
- | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
- }
- historyItem.eventTag = historyItem.localEventTag;
- historyItem.eventTag.uid = uid;
- historyItem.eventTag.string = "gnss";
- return historyItem;
+ mHistoryItem.time = mMonotonicClock.monotonicTime();
+ mHistoryItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
+ setGnssSignalLevel(BatteryStats.HistoryItem.GNSS_SIGNAL_QUALITY_NONE);
+ return mHistoryItem;
}
- private BatteryStats.HistoryItem buildHistoryItem(int timestamp, int signalLevel) {
+ private BatteryStats.HistoryItem buildHistoryItem(long timestamp, boolean stateOn,
+ int uid) {
mStatsRule.setTime(timestamp, timestamp);
- BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
- historyItem.time = mMonotonicClock.monotonicTime();
- historyItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
- historyItem.states2 =
- signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
- return historyItem;
+ mHistoryItem.time = mMonotonicClock.monotonicTime();
+ mHistoryItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
+ if (stateOn) {
+ mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_START;
+ } else {
+ mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+ }
+ mHistoryItem.eventTag = mHistoryItem.localEventTag;
+ mHistoryItem.eventTag.uid = uid;
+ mHistoryItem.eventTag.string = "gnss";
+ return mHistoryItem;
+ }
+
+ private BatteryStats.HistoryItem buildHistoryItem(long timestamp, int signalLevel) {
+ mStatsRule.setTime(timestamp, timestamp);
+ mHistoryItem.time = mMonotonicClock.monotonicTime();
+ setGnssSignalLevel(signalLevel);
+ mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_NONE;
+ mHistoryItem.eventTag = null;
+ return mHistoryItem;
+ }
+
+ private void setGnssSignalLevel(int signalLevel) {
+ mHistoryItem.states2 =
+ (mHistoryItem.states2 & ~BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+ | signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
}
private int[] states(int... states) {
@@ -362,12 +456,14 @@
AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
PowerComponentAggregatedPowerStats powerComponentStats =
aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_GNSS);
- powerComponentStats.start(0);
+ powerComponentStats.start(START_TIME);
- powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
- powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
- powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
- powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+ powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, START_TIME);
+ powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, START_TIME);
+ powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND,
+ START_TIME);
+ powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED,
+ START_TIME);
return powerComponentStats;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index f477682..6ac95c8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -109,6 +109,8 @@
private AidlResponseHandler mAidlResponseHandler;
@Mock
private AuthenticationStateListeners mAuthenticationStateListeners;
+ @Mock
+ private BiometricUtils<Face> mBiometricUtils;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -213,7 +215,7 @@
mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
true /* debugConsent */,
(new FaceEnrollOptions.Builder()).setEnrollReason(ENROLL_SOURCE).build(),
- mAuthenticationStateListeners);
+ mAuthenticationStateListeners, mBiometricUtils);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 6780e60..bf97086 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -50,6 +50,7 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.UserSwitchProvider;
+import com.android.server.biometrics.sensors.face.FaceUtils;
import org.junit.Before;
import org.junit.Test;
@@ -90,6 +91,8 @@
private AidlSession mCurrentSession;
@Mock
private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ private FaceUtils mBiometricUtils;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -114,7 +117,7 @@
mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback, mBiometricUtils);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
index 4248e5e..24ce569 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -206,7 +206,7 @@
new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
false /* debugConsent */, (new FaceEnrollOptions.Builder()).build(),
- mAuthenticationStateListeners));
+ mAuthenticationStateListeners, mBiometricUtils));
mLooper.dispatchAll();
verify(mAidlResponseHandlerCallback).onEnrollSuccess();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 698db2e..4ef8782 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -51,6 +51,7 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.UserSwitchProvider;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
@@ -96,6 +97,8 @@
private HandlerThread mThread;
@Mock
AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ private FingerprintUtils mBiometricUtils;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -121,7 +124,7 @@
mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback, mBiometricUtils);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 51f64ba..b97a268 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
import static android.app.Notification.COLOR_DEFAULT;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
@@ -36,6 +37,7 @@
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS;
import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY;
import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
import static com.android.server.notification.GroupHelper.BASE_FLAGS;
@@ -2204,7 +2206,7 @@
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
- eq(expectedGroupKey_silent), eq(false));
+ eq(expectedGroupKey_silent), eq(true));
// Check that the alerting section group is removed
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2264,13 +2266,15 @@
notificationList);
// Check that channel1's notifications are moved to the silent section group
- expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
- mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
- "TEST_CHANNEL_ID1");
- verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT/2 + 1)).addAutoGroup(anyString(),
- eq(expectedGroupKey_silent), eq(false));
+ // But not enough to auto-group => remove override group key
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())) {
+ assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+ }
+ }
// Check that the alerting section group is not removed, only updated
expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
@@ -2343,7 +2347,7 @@
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(numSilentGroupNotifications)).addAutoGroup(anyString(),
- eq(expectedGroupKey_silent), eq(false));
+ eq(expectedGroupKey_silent), eq(true));
// Check that the alerting section group is removed
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2353,6 +2357,60 @@
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAutogroup_updateChannel_reachedMinAutogroupCount() {
+ final String pkg = "package";
+ final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+ final NotificationChannel channel2 = new NotificationChannel("TEST_CHANNEL_ID2",
+ "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post notifications with different channels that would autogroup in different sections
+ NotificationRecord r;
+ // Not enough notifications to autogroup initially
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ if (i % 2 == 0) {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, null, false, channel1);
+ } else {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, null, false, channel2);
+ }
+ notificationList.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
+ }
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ Mockito.reset(mCallback);
+
+ // Update channel1's importance
+ final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ channel1.setImportance(IMPORTANCE_LOW);
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())) {
+ record.updateNotificationChannel(channel1);
+ }
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
+ notificationList);
+
+ // Check that channel1's notifications are moved to the silent section & autogroup all
+ NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+ mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ "TEST_CHANNEL_ID1");
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_silent), eq(true));
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
+ }
+
+ @Test
@EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
public void testNoGroup_singletonGroup_underLimit() {
@@ -2519,7 +2577,11 @@
assertThat(cachedSummary).isNull();
}
- private void checkNonGroupableNotifications() {
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
+ public void testNonGroupableNotifications() {
+ // Check that there is no valid section for: conversations, calls, foreground services
NotificationRecord notification_conversation = mock(NotificationRecord.class);
when(notification_conversation.isConversation()).thenReturn(true);
assertThat(GroupHelper.getSection(notification_conversation)).isNull();
@@ -2592,8 +2654,6 @@
"", false, recsChannel);
assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
"AlertingSection");
-
- checkNonGroupableNotifications();
}
@Test
@@ -2638,8 +2698,86 @@
"", false, recsChannel);
assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
"RecsSection");
+ }
- checkNonGroupableNotifications();
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+ public void testNonGroupableNotifications_forceGroupConversations() {
+ // Check that there is no valid section for: calls, foreground services
+ NotificationRecord notification_call = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ when(notification_call.isConversation()).thenReturn(false);
+ when(notification_call.getNotification()).thenReturn(n);
+ when(notification_call.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_call)).isNull();
+
+ NotificationRecord notification_colorFg = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ n = mock(Notification.class);
+ when(notification_colorFg.isConversation()).thenReturn(false);
+ when(notification_colorFg.getNotification()).thenReturn(n);
+ when(notification_colorFg.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isForegroundService()).thenReturn(true);
+ when(n.isColorized()).thenReturn(true);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+ @DisableFlags(FLAG_SORT_SECTION_BY_TIME)
+ public void testConversationGroupSections_disableSortSectionByTime() {
+ // Check that there are separate sections for conversations: alerting and silent
+ NotificationRecord notification_conversation_silent = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_LOW);
+ notification_conversation_silent = spy(notification_conversation_silent);
+ when(notification_conversation_silent.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_silent).mName).isEqualTo(
+ "PeopleSection(silent)");
+
+ // Check that there is a correct section for conversations
+ NotificationRecord notification_conversation_alerting = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_DEFAULT);
+ notification_conversation_alerting = spy(notification_conversation_alerting);
+ when(notification_conversation_alerting.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_alerting).mName).isEqualTo(
+ "PeopleSection(alerting)");
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS,
+ FLAG_SORT_SECTION_BY_TIME})
+ public void testConversationGroupSections() {
+ // Check that there is a single section for silent/alerting conversations
+ NotificationRecord notification_conversation_silent = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_LOW);
+ notification_conversation_silent = spy(notification_conversation_silent);
+ when(notification_conversation_silent.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_silent).mName).isEqualTo(
+ "PeopleSection");
+
+ NotificationRecord notification_conversation_alerting = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_DEFAULT);
+ notification_conversation_alerting = spy(notification_conversation_alerting);
+ when(notification_conversation_alerting.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_alerting).mName).isEqualTo(
+ "PeopleSection");
+
+ // Check that there is a section for priority conversations
+ NotificationRecord notification_conversation_prio = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_DEFAULT);
+ notification_conversation_prio = spy(notification_conversation_prio);
+ when(notification_conversation_prio.isConversation()).thenReturn(true);
+ notification_conversation_prio.getChannel().setImportantConversation(true);
+ assertThat(GroupHelper.getSection(notification_conversation_prio).mName).isEqualTo(
+ "PeopleSection(priority)");
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f10370..c1e3f47 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2186,6 +2186,80 @@
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testReadXml_upgradeToModesUi_resetsImplicitRuleIcon() throws Exception {
+ setupZenConfig();
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ ZenRule implicitRuleWithModesUi = expectedImplicitRule("pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, POLICY, null);
+
+ // Add one implicit rule in the pre-MODES_UI configuration.
+ ZenRule implicitRuleBeforeModesUi = implicitRuleWithModesUi.copy();
+ implicitRuleBeforeModesUi.iconResName = "pkg_icon";
+ mZenModeHelper.mConfig.automaticRules.put(implicitRuleBeforeModesUi.id,
+ implicitRuleBeforeModesUi);
+ // Plus one other normal rule.
+ ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
+ anotherRule.id = "other_rule";
+ anotherRule.iconResName = "other_icon";
+ anotherRule.type = TYPE_IMMERSIVE;
+ mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
+
+ // Write with pre-modes-ui = (modes_api) version, then re-read.
+ ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_API);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // Implicit rule was updated.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleBeforeModesUi.id))
+ .isEqualTo(implicitRuleWithModesUi);
+
+ // The other rule was untouched.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(anotherRule.id))
+ .isEqualTo(anotherRule);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testReadXml_onModesUi_implicitRulesUntouched() throws Exception {
+ setupZenConfig();
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ // Add one implicit rule already in its post-modes-UI configuration, also customized with
+ // an icon;
+ ZenRule implicitRuleWithModesUi = expectedImplicitRule("pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, POLICY, null);
+ implicitRuleWithModesUi.iconResName = "icon_chosen_by_user";
+ mZenModeHelper.mConfig.automaticRules.put(implicitRuleWithModesUi.id,
+ implicitRuleWithModesUi);
+
+ // Plus one other normal rule.
+ ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
+ anotherRule.id = "other_rule";
+ anotherRule.iconResName = "other_icon";
+ anotherRule.type = TYPE_IMMERSIVE;
+ mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
+
+ // Write with modes_ui version, then re-read.
+ ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // Both rules were untouched
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleWithModesUi.id))
+ .isEqualTo(implicitRuleWithModesUi);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(anotherRule.id))
+ .isEqualTo(anotherRule);
+ }
+
+ @Test
public void testCountdownConditionSubscription() throws Exception {
ZenModeConfig config = new ZenModeConfig();
mZenModeHelper.mConfig = config;
@@ -6852,7 +6926,9 @@
rule.zenPolicy = policy;
rule.pkg = ownerPkg;
rule.name = CUSTOM_APP_LABEL;
- rule.iconResName = ICON_RES_NAME;
+ if (!Flags.modesUi()) {
+ rule.iconResName = ICON_RES_NAME;
+ }
rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
CUSTOM_APP_LABEL);
rule.type = AutomaticZenRule.TYPE_OTHER;
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 43ad44f..2549ff5 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -32,11 +32,11 @@
"frameworks-base-testutils",
"frameworks-services-vibrator-testutils",
"junit",
- "junit-params",
"mockito-target-inline-minus-junit4",
"platform-test-annotations",
"service-permission.stubs.system_server",
"services.core",
+ "TestParameterInjector",
],
jni_libs: ["libdexmakerjvmtiagent"],
platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index e0d05df..2d312d2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -18,32 +18,36 @@
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
+import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED;
+import static android.os.vibrator.Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES;
import static com.android.internal.R.xml.haptic_feedback_customization;
-import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
+import static com.android.internal.R.xml.haptic_feedback_customization_source_rotary_encoder;
+import static com.android.internal.R.xml.haptic_feedback_customization_source_touchscreen;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
-import android.os.vibrator.Flags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
-import android.util.SparseArray;
+import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
-import com.android.internal.annotations.Keep;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
import org.junit.Rule;
@@ -56,7 +60,7 @@
import java.io.File;
import java.io.FileOutputStream;
-@RunWith(JUnitParamsRunner.class)
+@RunWith(TestParameterInjector.class)
public class HapticFeedbackCustomizationTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -69,166 +73,173 @@
private static final VibrationEffect COMPOSITION_VIBRATION =
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
- private static final String PREDEFINED_VIBRATION_XML =
+ private static final String PREDEFINED_VIBRATION_CLICK_XML =
"<vibration-effect><predefined-effect name=\"click\"/></vibration-effect>";
- private static final VibrationEffect PREDEFINED_VIBRATION =
+ private static final VibrationEffect PREDEFINED_VIBRATION_CLICK =
VibrationEffect.createPredefined(EFFECT_CLICK);
+ private static final String PREDEFINED_VIBRATION_TICK_XML =
+ "<vibration-effect><predefined-effect name=\"tick\"/></vibration-effect>";
+ private static final VibrationEffect PREDEFINED_VIBRATION_TICK =
+ VibrationEffect.createPredefined(EFFECT_TICK);
+
private static final String WAVEFORM_VIBRATION_XML = "<vibration-effect>"
+ "<waveform-effect>"
+ "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>"
+ "</waveform-effect>"
+ "</vibration-effect>";
- private static final VibrationEffect WAVEFORM_VIBARTION =
+ private static final VibrationEffect WAVEFORM_VIBRATION =
VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1);
@Mock private Resources mResourcesMock;
@Mock private VibratorInfo mVibratorInfoMock;
- @Keep
- private static Object[][] hapticFeedbackCustomizationTestArguments() {
- // (boolean hasConfigFile, boolean hasRes).
- return new Object[][] {{true, true}, {true, false}, {false, true}};
+ private enum CustomizationSource {
+ DEVICE_CONFIG_FILE,
+ DEVICE_RESOURCE,
+ DEVICE_RESOURCE_INPUT_ROTARY,
+ DEVICE_RESOURCE_INPUT_TOUCHSCREEN
}
@Before
public void setUp() {
+ clearFileAndResourceSetup();
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
- mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
- mSetFlagsRule.disableFlags(
- Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_noCustomization_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xml = "<haptic-feedback-constants></haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- setupParseCustomizations(xml, hasConfigFile, hasRes);
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_featureFlagDisabled_returnsNull(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+ public void testParseCustomizations_featureFlagDisabled_customizationNotLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
// Valid customization XML.
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- setupParseCustomizations(xml, hasConfigFile, hasRes);
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
.isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
public void testParseCustomizations_oneVibrationCustomization_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xml = "<haptic-feedback-constants>"
- + "<constant id=\"10\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(10, COMPOSITION_VIBRATION);
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_oneVibrationSelectCustomization_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xml = "<haptic-feedback-constants>"
- + "<constant id=\"10\">"
- + "<vibration-select>"
- + COMPOSITION_VIBRATION_XML
- + "</vibration-select>"
- + "</constant>"
- + "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(10, COMPOSITION_VIBRATION);
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_multipleCustomizations_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xml = "<haptic-feedback-constants>"
- + "<constant id=\"1\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "<constant id=\"12\">"
- + "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
- + WAVEFORM_VIBRATION_XML
- + "</vibration-select>"
- + "</constant>"
- + "<constant id=\"150\">"
- + PREDEFINED_VIBRATION_XML
- + "</constant>"
- + "<constant id=\"10\">"
- + "<vibration-select>"
- + WAVEFORM_VIBRATION_XML
- + COMPOSITION_VIBRATION_XML
- + "</vibration-select>"
- + "</constant>"
- + "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(1, COMPOSITION_VIBRATION);
- expectedMapping.put(12, PREDEFINED_VIBRATION);
- expectedMapping.put(150, PREDEFINED_VIBRATION);
- expectedMapping.put(10, WAVEFORM_VIBARTION);
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
- boolean hasConfigFile, boolean hasRes)
- throws Exception {
- makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
- String xml = "<haptic-feedback-constants>"
- + "<constant id=\"1\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "<constant id=\"12\">"
- + "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
- + WAVEFORM_VIBRATION_XML
- + "</vibration-select>"
- + "</constant>"
- + "<constant id=\"150\">"
- + PREDEFINED_VIBRATION_XML
- + "</constant>"
- + "<constant id=\"10\">"
- + "<vibration-select>"
- + WAVEFORM_VIBRATION_XML
- + COMPOSITION_VIBRATION_XML
- + "</vibration-select>"
- + "</constant>"
- + "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
- boolean hasConfigFile, boolean hasRes)
+ @TestParameter CustomizationSource customizationSource)
throws Exception {
- makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isEqualTo(COMPOSITION_VIBRATION);
+ }
+
+ @Test
+ public void testParseCustomizations_oneVibrationSelectCustomization_success(
+ @TestParameter CustomizationSource customizationSource)
+ throws Exception {
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isEqualTo(COMPOSITION_VIBRATION);
+ }
+
+ @Test
+ public void testParseCustomizations_multipleCustomizations_success(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"1\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"12\">"
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "<constant id=\"150\">"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + "</constant>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+ customization))
+ .isEqualTo(COMPOSITION_VIBRATION);
+ assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+ customization))
+ .isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+ customization))
+ .isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customization))
+ .isEqualTo(WAVEFORM_VIBRATION);
+ }
+
+ @Test
+ public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION_CLICK, WAVEFORM_VIBRATION);
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"1\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"12\">"
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "<constant id=\"150\">"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + "</constant>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customization)).isNull();
+ }
+
+ @Test
+ public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ makeSupported(PREDEFINED_VIBRATION_CLICK, WAVEFORM_VIBRATION);
makeUnsupported(COMPOSITION_VIBRATION);
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">" // No supported customization.
@@ -236,68 +247,89 @@
+ "</constant>"
+ "<constant id=\"12\">" // PREDEFINED_VIBRATION is the first/only supported.
+ "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ COMPOSITION_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
- + "<constant id=\"14\">" // WAVEFORM_VIBARTION is the first/only supported.
+ + "<constant id=\"14\">" // WAVEFORM_VIBRATION is the first/only supported.
+ "<vibration-select>"
+ COMPOSITION_VIBRATION_XML
+ WAVEFORM_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
+ "<constant id=\"150\">" // PREDEFINED_VIBRATION is the first/only supported.
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "<constant id=\"10\">" // PREDEFINED_VIBRATION is the first supported.
+ "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ WAVEFORM_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(12, PREDEFINED_VIBRATION);
- expectedMapping.put(14, WAVEFORM_VIBARTION);
- expectedMapping.put(150, PREDEFINED_VIBRATION);
- expectedMapping.put(10, PREDEFINED_VIBRATION);
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+ customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 14, customizationSource,
+ customization)).isEqualTo(WAVEFORM_VIBRATION);
+ assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+ customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
}
@Test
- public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
- setCustomizationFilePath("");
+ public void testParseCustomizations_malformedXml_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ // No end "<constant>" tag
+ String xmlNoEndConstantTag = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customizationNoEndConstantTag = createCustomizationForSource(
+ xmlNoEndConstantTag, customizationSource);
+ // No start "<haptic-feedback-constants>" tag
+ String xmlNoStartCustomizationTag = "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationNoStartCustomizationTag =
+ createCustomizationForSource(xmlNoStartCustomizationTag, customizationSource);
+ // No end "<haptic-feedback-constants>" tag
+ String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationNoEndCustomizationTag =
+ createCustomizationForSource(xmlNoEndCustomizationTag, customizationSource);
+ // No start "<constant>" tag
+ String xmlNoStartConstantTag = "<haptic-feedback-constants>"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationNoStartConstantTag = createCustomizationForSource(
+ xmlNoStartConstantTag, customizationSource);
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
-
- setCustomizationFilePath(null);
-
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
-
- setCustomizationFilePath("non_existent_file.xml");
-
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoEndConstantTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoStartCustomizationTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoEndCustomizationTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoStartConstantTag)).isNull();
}
@Test
- public void testParseCustomizations_noCustomizationResource_returnsNull() throws Exception {
- mSetFlagsRule.enableFlags(
- Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
- doThrow(new Resources.NotFoundException())
- .when(mResourcesMock).getXml(haptic_feedback_customization);
-
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ public void testParseCustomizations_disallowedVibrationForHapticFeedback_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
// The XML content is good, but the serialized vibration is not supported for haptic
// feedback usage (i.e. repeating vibration).
String xml = "<haptic-feedback-constants>"
@@ -311,185 +343,245 @@
+ "</vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_emptyXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- assertParseCustomizationsFails("", hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_noVibrationXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ public void testParseCustomizations_xmlNoVibration_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource, customization))
+ .isNull();
}
+
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_badEffectId_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- // Negative id
+ public void testParseCustomizations_badEffectId_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
String xmlNegativeId = "<haptic-feedback-constants>"
+ "<constant id=\"-10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
- // Non-numeral id
- String xmlNonNumericalId = "<haptic-feedback-constants>"
- + "<constant id=\"xyz\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(
+ xmlNegativeId, customizationSource);
- assertParseCustomizationsFails(xmlNegativeId, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNonNumericalId, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ -10, customizationSource, customization))
+ .isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_malformedXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- // No start "<constant>" tag
- String xmlNoStartConstantTag = "<haptic-feedback-constants>"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "</haptic-feedback-constants>";
- // No end "<constant>" tag
- String xmlNoEndConstantTag = "<haptic-feedback-constants>"
- + "<constant id=\"10\">"
- + COMPOSITION_VIBRATION_XML
- + "</haptic-feedback-constants>";
- // No start "<haptic-feedback-constants>" tag
- String xmlNoStartCustomizationTag = "<constant id=\"10\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "</haptic-feedback-constants>";
- // No end "<haptic-feedback-constants>" tag
- String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
- + "<constant id=\"10\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>";
-
- assertParseCustomizationsFails(xmlNoStartConstantTag, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNoEndConstantTag, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNoStartCustomizationTag, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNoEndCustomizationTag, hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_badVibrationXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xmlBad1 = "<haptic-feedback-constants>"
+ public void testParseCustomizations_badVibrationXml_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ // Case#1 - bad opening tag <bad-vibration-effect>
+ String xmlBadTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<bad-vibration-effect></bad-vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBad2 = "<haptic-feedback-constants>"
+ HapticFeedbackCustomization customizationBadTag = createCustomizationForSource(
+ xmlBadTag, customizationSource);
+ // Case#2 - bad attribute "name" for tag <predefined-effect>
+ String xmlBadEffectName = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBad3 = "<haptic-feedback-constants>"
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationBadEffectName = createCustomizationForSource(
+ xmlBadEffectName, customizationSource);
+ // Case#3 - miss "</vibration-select>"
+ String xmlBadEffectNameAndMissingCloseTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBad4 = "<haptic-feedback-constants>"
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationBadEffectNameAndMissingCloseTag =
+ createCustomizationForSource(xmlBadEffectNameAndMissingCloseTag,
+ customizationSource);
+ // Case#4 - miss "<vibration-select>"
+ String xmlBadEffectNameAndMissingOpenTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationBadEffectNameAndMissingOpenTag =
+ createCustomizationForSource(xmlBadEffectNameAndMissingOpenTag,
+ customizationSource);
- assertParseCustomizationsFails(xmlBad1, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBad2, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBad3, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBad4, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadEffectName)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadEffectNameAndMissingCloseTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadEffectNameAndMissingOpenTag)).isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_badConstantAttribute_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xmlBadConstantAttribute1 = "<haptic-feedback-constants>"
+ public void testParseCustomizations_badConstantAttribute_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ // Case#1 - bad attribute id for tag <constant>
+ String xmlBadConstantIdAttribute = "<haptic-feedback-constants>"
+ "<constant iddddd=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBadConstantAttribute2 = "<haptic-feedback-constants>"
+ HapticFeedbackCustomization customizationBadConstantIdAttribute =
+ createCustomizationForSource(xmlBadConstantIdAttribute, customizationSource);
+ // Case#2 - unexpected attribute "unwanted" for tag <constant>
+ String xmlUnwantedConstantAttribute = "<haptic-feedback-constants>"
+ "<constant id=\"10\" unwanted-attr=\"1\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationUnwantedConstantAttribute =
+ createCustomizationForSource(xmlUnwantedConstantAttribute, customizationSource);
- assertParseCustomizationsFails(xmlBadConstantAttribute1, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBadConstantAttribute2, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadConstantIdAttribute)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationUnwantedConstantAttribute)).isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_duplicateEffects_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ public void testParseCustomizations_duplicateEffects_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
String xmlDuplicateEffect = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "<constant id=\"10\">"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "<constant id=\"11\">"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xmlDuplicateEffect,
+ customizationSource);
- assertParseCustomizationsFails(xmlDuplicateEffect, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isNull();
+ assertThat(getEffectForSource(/* effectId= */ 11, customizationSource, customization))
+ .isNull();
}
- private void assertParseCustomizationsSucceeds(String xml,
- SparseArray<VibrationEffect> expectedCustomizations, boolean hasConfigFile,
- boolean hasRes) throws Exception {
- setupParseCustomizations(xml, hasConfigFile, hasRes);
- assertThat(expectedCustomizations.contentEquals(
- HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
- .isTrue();
- }
-
- private void assertParseCustomizationsFails(String xml, boolean hasConfigFile, boolean hasRes)
+ @Test
+ public void testParseCustomizations_withDifferentCustomizations_loadsCorrectOne()
throws Exception {
- setupParseCustomizations(xml, hasConfigFile, hasRes);
- assertThrows("Expected haptic feedback customization to fail",
- CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(
- mResourcesMock, mVibratorInfoMock));
+ String xmlBaseCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"14\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ String xmlRotaryInputCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ String xmlTouchScreenInputCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + PREDEFINED_VIBRATION_TICK_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ setupCustomizations(xmlBaseCustomization, CustomizationSource.DEVICE_RESOURCE);
+ setupCustomizations(xmlRotaryInputCustomization,
+ CustomizationSource.DEVICE_RESOURCE_INPUT_ROTARY);
+ HapticFeedbackCustomization customization = createCustomizationForSource(
+ xmlTouchScreenInputCustomization,
+ CustomizationSource.DEVICE_RESOURCE_INPUT_TOUCHSCREEN);
+
+ // Matching customizations.
+ assertThat(customization.getEffect(/* effectId= */ 10)).isEqualTo(COMPOSITION_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 14)).isEqualTo(WAVEFORM_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 10,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(customization.getEffect(/* effectId= */ 10,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PREDEFINED_VIBRATION_TICK);
+ // Missing from input source customization xml. Fallback to base.
+ assertThat(customization.getEffect(/* effectId= */ 14,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(WAVEFORM_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 14,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(WAVEFORM_VIBRATION);
}
- private void setupParseCustomizations(String xml, boolean hasConfigFile, boolean hasRes)
+ @Test
+ public void testParseCustomizations_customizationsFromConfigFileAndRes_preferConfigFile()
throws Exception {
- clearFileAndResourceSetup();
- if (hasConfigFile) {
- setupCustomizationFile(xml);
- }
- if (hasRes) {
- setupCustomizationResource(xml);
+ String xmlConfigFileCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ String xmlResourceCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "<constant id=\"14\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ setupCustomizations(xmlConfigFileCustomization, CustomizationSource.DEVICE_CONFIG_FILE);
+ HapticFeedbackCustomization customization = createCustomizationForSource(
+ xmlResourceCustomization, CustomizationSource.DEVICE_RESOURCE);
+
+ // When config file and resource customizations are both available. Load the config file
+ // Customization.
+ assertThat(customization.getEffect(/* effectId= */ 10)).isEqualTo(COMPOSITION_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 14)).isNull();
+ }
+
+ private HapticFeedbackCustomization createCustomizationForSource(String xml,
+ CustomizationSource customizationSource) throws Exception {
+ setupCustomizations(xml, customizationSource);
+ return new HapticFeedbackCustomization(mResourcesMock, mVibratorInfoMock);
+ }
+
+ private void setupCustomizations(String xml, CustomizationSource customizationSource)
+ throws Exception {
+ switch (customizationSource) {
+ case DEVICE_CONFIG_FILE -> setupCustomizationFile(xml);
+ case DEVICE_RESOURCE -> setupCustomizationResource(xml, haptic_feedback_customization);
+ case DEVICE_RESOURCE_INPUT_ROTARY -> setupCustomizationResource(xml,
+ haptic_feedback_customization_source_rotary_encoder);
+ case DEVICE_RESOURCE_INPUT_TOUCHSCREEN -> setupCustomizationResource(xml,
+ haptic_feedback_customization_source_touchscreen);
}
}
- private void clearFileAndResourceSetup() {
- when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
- .thenReturn(null);
- when(mResourcesMock.getXml(haptic_feedback_customization)).thenReturn(null);
+ private void setupCustomizationResource(String xml, int xmlResId) throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ mSetFlagsRule.enableFlags(FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+ doReturn(FakeXmlResourceParser.fromXml(xml)).when(mResourcesMock).getXml(xmlResId);
}
private void setupCustomizationFile(String xml) throws Exception {
@@ -498,15 +590,34 @@
}
private void setCustomizationFilePath(String path) {
- when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
- .thenReturn(path);
+ doReturn(path).when(mResourcesMock)
+ .getString(R.string.config_hapticFeedbackCustomizationFile);
}
- private void setupCustomizationResource(String xml) throws Exception {
- mSetFlagsRule.enableFlags(
- Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
- when(mResourcesMock.getXml(haptic_feedback_customization))
- .thenReturn(FakeXmlResourceParser.fromXml(xml));
+ private void clearFileAndResourceSetup() {
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getString(R.string.config_hapticFeedbackCustomizationFile);
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getXml(haptic_feedback_customization);
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getXml(haptic_feedback_customization_source_rotary_encoder);
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getXml(haptic_feedback_customization_source_touchscreen);
+ }
+
+ @Nullable
+ private VibrationEffect getEffectForSource(int effectId,
+ CustomizationSource customizationSource,
+ HapticFeedbackCustomization hapticFeedbackCustomization) {
+ return switch (customizationSource) {
+ case DEVICE_CONFIG_FILE, DEVICE_RESOURCE -> hapticFeedbackCustomization.getEffect(
+ effectId);
+ case DEVICE_RESOURCE_INPUT_ROTARY -> hapticFeedbackCustomization.getEffect(effectId,
+ InputDevice.SOURCE_ROTARY_ENCODER);
+ case DEVICE_RESOURCE_INPUT_TOUCHSCREEN -> hapticFeedbackCustomization.getEffect(
+ effectId,
+ InputDevice.SOURCE_TOUCHSCREEN);
+ };
}
private void makeSupported(VibrationEffect... effects) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 8797e63..6076d33 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -21,16 +21,21 @@
import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_QUICK_RISE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD;
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
import static android.view.HapticFeedbackConstants.BIOMETRIC_CONFIRM;
import static android.view.HapticFeedbackConstants.BIOMETRIC_REJECT;
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+import static android.view.HapticFeedbackConstants.DRAG_START;
import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE;
import static android.view.HapticFeedbackConstants.KEYBOARD_TAP;
+import static android.view.HapticFeedbackConstants.NO_HAPTICS;
import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
@@ -42,6 +47,7 @@
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
@@ -52,11 +58,13 @@
import android.util.AtomicFile;
import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -75,6 +83,11 @@
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
+ private static final VibrationEffect PRIMITIVE_THUD_EFFECT =
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_THUD, 0.5497f).compose();
+ private static final VibrationEffect PRIMITIVE_QUICK_RISE_EFFECT =
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_QUICK_RISE,
+ 0.6497f).compose();
private static final int[] SCROLL_FEEDBACK_CONSTANTS =
new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
@@ -90,45 +103,52 @@
@Mock private Resources mResourcesMock;
- @Test
- public void testNonExistentCustomization_useDefault() throws Exception {
- // No customization file is set.
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TICK));
-
- // The customization file specifies no customization.
- setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
- hapticProvider = createProviderWithDefaultCustomizations();
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+ @Before
+ public void setUp() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
}
@Test
- public void testExceptionParsingCustomizations_useDefault() throws Exception {
- setupCustomizationFile("<bad-xml></bad-xml>");
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ public void testNonExistentCustomization_useDefault() {
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+ // No customization for `CLOCK_TICK`, so the default vibration is used.
+ assertThat(provider.getVibration(CLOCK_TICK)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(CLOCK_TICK,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(CLOCK_TICK, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
}
@Test
- public void testUseValidCustomizedVibration() throws Exception {
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+ public void testUseValidCustomizedVibration() {
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+ PRIMITIVE_QUICK_RISE);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsRotary = new SparseArray<>();
+ customizationsRotary.put(CONTEXT_CLICK, PRIMITIVE_TICK_EFFECT);
+ customizationsRotary.put(DRAG_START, PRIMITIVE_QUICK_RISE_EFFECT);
+ SparseArray<VibrationEffect> customizationsTouchScreen = new SparseArray<>();
+ customizationsTouchScreen.put(CONTEXT_CLICK, PRIMITIVE_THUD_EFFECT);
+ customizationsTouchScreen.put(DRAG_START, PRIMITIVE_CLICK_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsRotary, customizationsTouchScreen);
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
- // The override for `CONTEXT_CLICK` is used.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+ // The customization for `CONTEXT_CLICK`.
+ assertThat(provider.getVibration(CONTEXT_CLICK))
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
- // `CLOCK_TICK` has no override, so the default vibration is used.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(CONTEXT_CLICK,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_TICK_EFFECT);
+ assertThat(provider.getVibration(CONTEXT_CLICK,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PRIMITIVE_THUD_EFFECT);
+ // The customization for `DRAG_START`.
+ assertThat(provider.getVibration(DRAG_START,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+ assertThat(provider.getVibration(DRAG_START,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
}
@Test
@@ -140,92 +160,151 @@
+ "</constant>"
+ "</haptic-feedback-constants>";
setupCustomizationFile(xml);
-
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
// The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+ assertThat(provider.getVibration(CONTEXT_CLICK))
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
// `CLOCK_TICK` has no override, so the default vibration is used.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
+ assertThat(provider.getVibration(CLOCK_TICK))
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
}
@Test
- public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() throws Exception {
+ public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() {
mockHapticTextSupport(false);
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(
+ /* customizations= */ customizations,
+ /* customizationsForRotary= */ customizations,
+ /* customizationsForTouchScreen= */ customizations);
// Test with a customization available for `TEXT_HANDLE_MOVE`.
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+ assertThat(
+ provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
// Test with no customization available for `TEXT_HANDLE_MOVE`.
- hapticProvider = createProvider(/* customizations= */ null);
+ provider = createProviderWithoutCustomizations();
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+ assertThat(
+ provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
}
@Test
- public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() throws Exception {
+ public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() {
mockHapticTextSupport(true);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_THUD, PRIMITIVE_TICK);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
-
+ SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+ customizationsByRotary.put(TEXT_HANDLE_MOVE, PRIMITIVE_TICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+ customizationsByTouchScreen.put(TEXT_HANDLE_MOVE, PRIMITIVE_THUD_EFFECT);
// Test with a customization available for `TEXT_HANDLE_MOVE`.
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsByRotary, customizationsByTouchScreen);
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_TICK_EFFECT);
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
// Test with no customization available for `TEXT_HANDLE_MOVE`.
- hapticProvider = createProvider(/* customizations= */ null);
+ provider = createProviderWithoutCustomizations();
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN))
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
}
@Test
- public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource()
- throws Exception {
- mockSafeModeEnabledVibration(10, 20, 30, 40);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+ public void testFeedbackConstantNoHapticEffect_noVibrationRegardlessCustomizations() {
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_THUD, PRIMITIVE_TICK);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
- customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+ customizations.put(NO_HAPTICS, PRIMITIVE_CLICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+ customizationsByRotary.put(NO_HAPTICS, PRIMITIVE_TICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+ customizationsByTouchScreen.put(NO_HAPTICS, PRIMITIVE_THUD_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsByRotary, customizationsByTouchScreen);
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
-
- mockSafeModeEnabledVibration(null);
- hapticProvider = createProvider(customizations);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ // Whatever customization set to NO_HAPTICS, no vibration happens.
+ assertThat(provider.getVibration(NO_HAPTICS)).isNull();
+ assertThat(provider.getVibration(NO_HAPTICS, InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+ assertThat(provider.getVibration(NO_HAPTICS, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
}
@Test
- public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
- throws Exception {
+ public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource() {
mockSafeModeEnabledVibration(10, 20, 30, 40);
- HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD);
+ SparseArray<VibrationEffect> safeModeCustomizations = new SparseArray<>();
+ safeModeCustomizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+ SparseArray<VibrationEffect> safeModeCustomizationsByRotary = new SparseArray<>();
+ safeModeCustomizationsByRotary.put(SAFE_MODE_ENABLED, PRIMITIVE_THUD_EFFECT);
+ SparseArray<VibrationEffect> safeModeCustomizationsByTouchScreen = new SparseArray<>();
+ safeModeCustomizationsByTouchScreen.put(SAFE_MODE_ENABLED, PRIMITIVE_TICK_EFFECT);
+ HapticFeedbackVibrationProvider provider =
+ createProvider(safeModeCustomizations, safeModeCustomizationsByRotary,
+ safeModeCustomizationsByTouchScreen);
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
- .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_TICK_EFFECT);
+
+ // Resource changed
+ mockSafeModeEnabledVibration(null);
+ provider =
+ createProvider(safeModeCustomizations, safeModeCustomizationsByRotary,
+ safeModeCustomizationsByTouchScreen);
+
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_TICK_EFFECT);
}
@Test
- public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
- throws Exception {
- mockSafeModeEnabledVibration(null);
- HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);
+ public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed() {
+ mockSafeModeEnabledVibration(10, 20, 30, 40);
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED))
+ .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+ }
+
+ @Test
+ public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed() {
+ mockSafeModeEnabledVibration(null);
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isNull();
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isNull();
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isNull();
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isNull();
}
@Test
@@ -236,19 +315,25 @@
customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
// Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations);
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
- .isEqualTo(PRIMITIVE_TICK_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_TAP)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_RELEASE)).isEqualTo(PRIMITIVE_TICK_EFFECT);
// Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
- hapticProvider = createProviderWithDefaultCustomizations();
+ provider = createProviderWithoutCustomizations();
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+ assertThat(provider.getVibration(KEYBOARD_TAP))
.isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+ assertThat(provider.getVibration(KEYBOARD_RELEASE))
+ .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_TOUCHSCREEN))
.isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
}
@@ -256,25 +341,64 @@
public void testKeyboardHaptic_fixAmplitude_keyboardVibrationReturned() {
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ assertThat(provider.getVibration(KEYBOARD_TAP)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_RELEASE)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_TAP,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_RELEASE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_TAP,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_RELEASE,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ }
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
- .isEqualTo(VibrationEffect.startComposition()
- .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
- .compose());
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
- .isEqualTo(VibrationEffect.startComposition()
- .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
- .compose());
+ @Test
+ public void testKeyboardHaptic_withCustomizations_customEffectsUsed() {
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+ PRIMITIVE_QUICK_RISE);
+ SparseArray<VibrationEffect> customizations = new SparseArray<>();
+ customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT);
+ customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+ customizationsByRotary.put(KEYBOARD_TAP, PRIMITIVE_THUD_EFFECT);
+ customizationsByRotary.put(KEYBOARD_RELEASE, PRIMITIVE_QUICK_RISE_EFFECT);
+ SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+ customizationsByTouchScreen.put(KEYBOARD_TAP, PRIMITIVE_QUICK_RISE_EFFECT);
+ customizationsByTouchScreen.put(KEYBOARD_RELEASE, PRIMITIVE_THUD_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsByRotary, customizationsByTouchScreen);
+
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
}
@Test
public void testVibrationAttribute_biometricConstants_returnsCommunicationRequestUsage() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
}
@@ -282,9 +406,9 @@
@Test
public void testVibrationAttribute_forNotBypassingIntensitySettings() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
@@ -292,9 +416,9 @@
@Test
public void testVibrationAttribute_forByassingIntensitySettings() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
SAFE_MODE_ENABLED,
/* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0);
@@ -304,10 +428,10 @@
@Test
public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() {
mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
@@ -317,10 +441,10 @@
@Test
public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() {
mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
@@ -329,10 +453,10 @@
@Test
public void testVibrationAttribute_notIme_useTouchUsage() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
@@ -341,10 +465,10 @@
@Test
public void testVibrationAttribute_isIme_useImeFeedbackUsage() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0,
HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId)
@@ -354,20 +478,34 @@
@Test
public void testIsRestricted_biometricConstants_returnsTrue() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
- assertThat(hapticProvider.isRestrictedHapticFeedback(effectId)).isTrue();
+ assertThat(provider.isRestrictedHapticFeedback(effectId)).isTrue();
}
}
- private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
- return createProvider(/* customizations= */ null);
+ private HapticFeedbackVibrationProvider createProviderWithoutCustomizations() {
+ return createProvider(/* customizations= */ new SparseArray<>(),
+ /* customizationsRotary= */ new SparseArray<>(),
+ /* customizationsTouchScreen */ new SparseArray<>());
}
private HapticFeedbackVibrationProvider createProvider(
SparseArray<VibrationEffect> customizations) {
- return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
+ return createProvider(customizations, /* customizationsRotary= */ new SparseArray<>(),
+ /* customizationsTouchScreen */ new SparseArray<>());
+ }
+
+ private HapticFeedbackVibrationProvider createProvider(
+ @NonNull SparseArray<VibrationEffect> customizations,
+ @NonNull SparseArray<VibrationEffect> customizationsRotary,
+ @NonNull SparseArray<VibrationEffect> customizationsTouchScreen) {
+ return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo,
+ new HapticFeedbackCustomization(
+ customizations,
+ customizationsRotary,
+ customizationsTouchScreen));
}
private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4013587..4afb562 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
@@ -192,6 +194,11 @@
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private final SparseArray<VibrationEffect> mHapticFeedbackVibrationMap = new SparseArray<>();
+ private final SparseArray<VibrationEffect> mHapticFeedbackVibrationMapSourceRotary =
+ new SparseArray<>();
+ private final SparseArray<VibrationEffect> mHapticFeedbackVibrationMapSourceTouchScreen =
+ new SparseArray<>();
+
private final List<HalVibration> mPendingVibrations = new ArrayList<>();
private VibratorManagerService mService;
@@ -339,8 +346,10 @@
@Override
HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
Resources resources, VibratorInfo vibratorInfo) {
- return new HapticFeedbackVibrationProvider(
- resources, vibratorInfo, mHapticFeedbackVibrationMap);
+ return new HapticFeedbackVibrationProvider(resources, vibratorInfo,
+ new HapticFeedbackCustomization(mHapticFeedbackVibrationMap,
+ mHapticFeedbackVibrationMapSourceRotary,
+ mHapticFeedbackVibrationMapSourceTouchScreen));
}
@Override
@@ -1475,6 +1484,60 @@
}
@Test
+ public void performHapticFeedbackForInputDevice_doesNotRequireVibrateOrBypassPermissions()
+ throws Exception {
+ // Deny permissions that would have been required for regular vibrations, and check that
+ // the vibration proceed as expected to verify that haptic feedback does not need these
+ // permissions.
+ denyPermission(android.Manifest.permission.VIBRATE);
+ denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+ denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+ // Flag override to enable the scroll feedback constants to bypass interruption policies.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.SCROLL_TICK,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.DRAG_START,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration vibrationByRotary =
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+ HalVibration vibrationByTouchScreen =
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.DRAG_START, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ // 2 haptics: 1 by rotary + 1 by touch screen
+ assertEquals(2, playedSegments.size());
+ // Verify feedback by rotary input
+ PrebakedSegment segmentByRotary = (PrebakedSegment) playedSegments.get(0);
+ assertEquals(VibrationEffect.EFFECT_CLICK, segmentByRotary.getEffectId());
+ VibrationAttributes attrsByRotary = vibrationByRotary.callerInfo.attrs;
+ assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrsByRotary.getUsage());
+ assertTrue(attrsByRotary.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+ assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+ // Verify feedback by touch screen input
+ PrebakedSegment segmentByTouchScreen = (PrebakedSegment) playedSegments.get(1);
+ assertEquals(VibrationEffect.EFFECT_TICK, segmentByTouchScreen.getEffectId());
+ VibrationAttributes attrsByTouchScreen = vibrationByTouchScreen.callerInfo.attrs;
+ assertEquals(VibrationAttributes.USAGE_TOUCH, attrsByTouchScreen.getUsage());
+ assertTrue(attrsByRotary.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+ assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+ }
+
+ @Test
public void performHapticFeedback_restrictedConstantsWithoutPermission_doesNotVibrate()
throws Exception {
// Deny permission to vibrate with restricted constants
@@ -1506,6 +1569,42 @@
}
@Test
+ public void performHapticFeedbackForInputDevice_restrictedConstantsWithoutPermission_doesNotVibrate()
+ throws Exception {
+ // Deny permission to vibrate with restricted constants
+ denyPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ // Public constant, no permission required
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ // Hidden system-only constant, permission required
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(
+ VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ // This vibrates.
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ false);
+ // This doesn't.
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ false);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ assertEquals(1, playedSegments.size());
+ PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
+ assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
+ }
+
+ @Test
public void performHapticFeedback_restrictedConstantsWithPermission_playsVibration()
throws Exception {
// Grant permission to vibrate with restricted constants
@@ -1539,33 +1638,95 @@
}
@Test
+ public void performHapticFeedbackForInputDevice_restrictedConstantsWithPermission_playsVibration()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ // Grant permission to vibrate with restricted constants
+ grantPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+ // Public constant, no permission required
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ // Hidden system-only constant, permission required
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(
+ VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ false);
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ false);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ assertEquals(2, playedSegments.size());
+ assertEquals(VibrationEffect.EFFECT_CLICK,
+ ((PrebakedSegment) playedSegments.get(0)).getEffectId());
+ assertEquals(VibrationEffect.EFFECT_HEAVY_CLICK,
+ ((PrebakedSegment) playedSegments.get(1)).getEffectId());
+ }
+
+ @Test
public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
denyPermission(android.Manifest.permission.VIBRATE);
mHapticFeedbackVibrationMap.put(
HapticFeedbackConstants.KEYBOARD_TAP,
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD));
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setVibratorInfoLoadSuccessful(false);
- fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK,
+ VibrationEffect.EFFECT_THUD);
VibratorManagerService service = createService();
+ // performHapticFeedback.
performHapticFeedbackAndWaitUntilFinished(
service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+ // performHapticFeedbackForInputDevice.
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.KEYBOARD_TAP, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.KEYBOARD_TAP, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
assertTrue(fakeVibrator.getAllEffectSegments().isEmpty());
}
@Test
public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
denyPermission(android.Manifest.permission.VIBRATE);
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
// These are bad haptic feedback IDs, so expect no vibration played.
+ // Test performHapticFeedback
performHapticFeedbackAndWaitUntilFinished(service, /* constant= */ -1, /* always= */ false);
performHapticFeedbackAndWaitUntilFinished(
service, HapticFeedbackConstants.NO_HAPTICS, /* always= */ true);
+ // Test performHapticFeedbackForInputDevice
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, /* constant= */ -1, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, /* constant= */ -1, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
}
@@ -1582,6 +1743,17 @@
}
@Test
+ public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+
+ assertTrue(vibration.callerToken == service);
+ }
+
+ @Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
// Deny permission to vibrate with vendor effects
@@ -2761,6 +2933,21 @@
return vib;
}
+ private HalVibration performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ VibratorManagerService service, int constant, int inputDeviceId, int inputSource,
+ boolean always) throws InterruptedException {
+ HalVibration vib = service.performHapticFeedbackForInputDeviceInternal(UID,
+ Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, constant, inputDeviceId, inputSource,
+ "some reason", service,
+ always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
+ 0 /* privFlags */);
+ if (vib != null) {
+ vib.waitForEnd();
+ }
+
+ return vib;
+ }
+
private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service,
VibrationEffect effect, VibrationAttributes attrs) throws InterruptedException {
return vibrateAndWaitUntilFinished(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 9950541..b6e393d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,10 +39,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -51,9 +49,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.graphics.Rect;
import android.os.Binder;
@@ -377,41 +373,6 @@
}
@Test
- public void testDelayWhileRecents() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- doReturn(false).when(dc).onDescendantOrientationChanged(any());
- final Task task = createTask(dc);
-
- // Simulate activity1 launches activity2.
- final ActivityRecord activity1 = createActivityRecord(task);
- activity1.setVisible(true);
- activity1.setVisibleRequested(false);
- activity1.allDrawn = true;
- final ActivityRecord activity2 = createActivityRecord(task);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
- activity2.allDrawn = true;
-
- dc.mClosingApps.add(activity1);
- dc.mOpeningApps.add(activity2);
- dc.prepareAppTransition(TRANSIT_OPEN);
- assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
-
- // Wait until everything in animation handler get executed to prevent the exiting window
- // from being removed during WindowSurfacePlacer Traversal.
- waitUntilHandlersIdle();
-
- // Start recents
- doReturn(true).when(task)
- .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
-
- dc.mAppTransitionController.handleAppTransitionReady();
-
- verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
- verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
- }
-
- @Test
public void testGetAnimationStyleResId() {
// Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
// specifying window type.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 1e8c3b2..6f7d0dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -36,7 +36,6 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
@@ -728,51 +727,6 @@
}
}
- @android.platform.test.annotations.RequiresFlagsDisabled(
- com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
- @SetupWindows(addWindows = W_INPUT_METHOD)
- @Test
- public void testLaunchRemoteAnimationWithoutImeBehind() {
- final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
- final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
-
- // Simulating win1 has shown IME and being IME layering/input target
- mDisplayContent.setImeLayeringTarget(win1);
- mDisplayContent.setImeInputTarget(win1);
- mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
- spyOn(mDisplayContent);
- mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
- makeWindowVisibleAndDrawn(mImeWindow);
- assertTrue(mImeWindow.isOnScreen());
- assertFalse(mImeWindow.isParentWindowHidden());
-
- try {
- // Simulating now win1 is being covered by the lockscreen which has no surface,
- // and then launching an activity win2 with the remote animation
- win1.mHasSurface = false;
- win1.mActivityRecord.setVisibility(false);
- mDisplayContent.mOpeningApps.add(win2.mActivityRecord);
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win2.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
-
- mDisplayContent.applySurfaceChangesTransaction();
- mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
- any(), any(), any(), any());
- // Verify the IME window won't apply surface change transaction with forAllImeWindows
- verify(mDisplayContent, never()).forAllImeWindows(any(), eq(true));
- } catch (Exception e) {
- // no-op
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6c8a7ac..9981a4d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -23,7 +23,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -96,18 +95,18 @@
@Test
public void testRunAnimation() {
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
- ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION);
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
assertAnimating(mAnimatable);
verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
- verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_RECENTS),
+ verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
callbackCaptor.capture());
- callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_RECENTS, mSpec);
+ callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
- assertEquals(ANIMATION_TYPE_RECENTS, mAnimatable.mFinishedAnimationType);
+ assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
verify(mTransaction).remove(eq(mAnimatable.mLeash));
// TODO: Verify reparenting once we use mPendingTransaction to reparent it back
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 22def51..72935cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -62,8 +62,6 @@
verify(c).accept(eq(mImeWindow));
}
- @android.platform.test.annotations.RequiresFlagsEnabled(
- com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
@SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testTraverseImeRegardlessOfImeTarget() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index d537bd7..88ce3a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -40,14 +40,12 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -405,30 +403,6 @@
}
@Test
- public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() {
- final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
- mAppWindow.mActivityRecord, "imeAppTarget");
- mDisplayContent.setImeInputTarget(imeAppTarget);
- mDisplayContent.setImeLayeringTarget(imeAppTarget);
- mDisplayContent.setImeControlTarget(imeAppTarget);
- mDisplayContent.updateImeParent();
-
- // Simulate the ime layering target task is animating with recents animation.
- final Task imeAppTargetTask = imeAppTarget.getTask();
- final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator;
- spyOn(imeTargetTaskAnimator);
- doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType();
- doReturn(true).when(imeTargetTaskAnimator).isAnimating();
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- // Ime should on top of the application window when in recents animation and keep
- // attached on app.
- assertTrue(mDisplayContent.shouldImeAttachedToApp());
- assertWindowHigher(mImeWindow, imeAppTarget);
- }
-
- @Test
public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() {
final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
mAppWindow.mActivityRecord, "imeAppTarget");
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index e6fe406..83dac18 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -175,8 +175,15 @@
* <p>
* The call recording tone is a 1400 hz tone which repeats every 15 seconds while recording is
* in progress.
+ *
+ * @deprecated this API was only intended to prevent call recording via the microphone by an app
+ * while in a phone call. Audio policies no longer make this possible. Further, this API was
+ * never actually used. Call recording solutions integrated in an OEM dialer app must use
+ * appropriate recording signals to inform the caller/callee of the recording.
* @hide
*/
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ @Deprecated
@SystemApi
public static final String EXTRA_PLAY_CALL_RECORDING_TONE =
"android.telecom.extra.PLAY_CALL_RECORDING_TONE";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index afd5720..1ba496d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -420,6 +420,7 @@
* <p>
* Note: This requires the Telephony config_supports_telephony_audio_device overlay to be true
* in order to work.
+ * @deprecated this functionality was never used and is no longer supported.
* @hide
*/
public static final String KEY_PLAY_CALL_RECORDING_TONE_BOOL = "play_call_recording_tone_bool";
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 05a68e9..6db5f82 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -798,6 +798,38 @@
.isEqualTo("My Test Debug Log Message true");
}
+ @Test
+ public void usesDefaultLogFromLevel() throws IOException {
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(LogLevel.WARN).build();
+ try {
+ traceMonitor.start();
+ mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "This message should not be logged");
+ mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP,
+ "This message should logged %d", 123);
+ mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP,
+ "This message should also be logged %d", 567);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(2);
+
+ Truth.assertThat(protolog.messages.get(0).getLevel())
+ .isEqualTo(LogLevel.WARN);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("This message should logged 123");
+
+ Truth.assertThat(protolog.messages.get(1).getLevel())
+ .isEqualTo(LogLevel.ERROR);
+ Truth.assertThat(protolog.messages.get(1).getMessage())
+ .isEqualTo("This message should also be logged 567");
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
index e3ec62d..aba6722 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -41,14 +41,14 @@
public class ProtoLogCommandHandlerTest {
@Mock
- ProtoLogService mProtoLogService;
+ ProtoLogConfigurationService mProtoLogConfigurationService;
@Mock
PrintWriter mPrintWriter;
@Test
public void printsHelpForAllAvailableCommands() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
cmdHandler.onHelp();
validateOnHelpPrinted();
@@ -57,7 +57,7 @@
@Test
public void printsHelpIfCommandIsNull() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
cmdHandler.onCommand(null);
validateOnHelpPrinted();
@@ -65,13 +65,13 @@
@Test
public void handlesGroupListCommand() {
- Mockito.when(mProtoLogService.getGroups())
+ Mockito.when(mProtoLogConfigurationService.getGroups())
.thenReturn(new String[] {"MY_TEST_GROUP", "MY_OTHER_GROUP"});
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "list" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "list" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("MY_TEST_GROUP"));
@@ -82,10 +82,10 @@
@Test
public void handlesIncompleteGroupsCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("Incomplete command"));
@@ -93,13 +93,14 @@
@Test
public void handlesGroupStatusCommand() {
- Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {"MY_GROUP"});
- Mockito.when(mProtoLogService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
+ Mockito.when(mProtoLogConfigurationService.getGroups())
+ .thenReturn(new String[] {"MY_GROUP"});
+ Mockito.when(mProtoLogConfigurationService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "status", "MY_GROUP" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("MY_GROUP"));
@@ -109,12 +110,12 @@
@Test
public void handlesGroupStatusCommandOfUnregisteredGroups() {
- Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {});
+ Mockito.when(mProtoLogConfigurationService.getGroups()).thenReturn(new String[] {});
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "status", "MY_GROUP" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("MY_GROUP"));
@@ -125,10 +126,10 @@
@Test
public void handlesGroupStatusCommandWithNoGroups() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "status" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("Incomplete command"));
@@ -137,10 +138,10 @@
@Test
public void handlesIncompleteLogcatCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("Incomplete command"));
@@ -149,50 +150,52 @@
@Test
public void handlesLogcatEnableCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "enable", "MY_GROUP" });
- Mockito.verify(mProtoLogService).enableProtoLogToLogcat("MY_GROUP");
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
- Mockito.verify(mProtoLogService)
+ Mockito.verify(mProtoLogConfigurationService)
.enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
}
@Test
public void handlesLogcatDisableCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "disable", "MY_GROUP" });
- Mockito.verify(mProtoLogService).disableProtoLogToLogcat("MY_GROUP");
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
- Mockito.verify(mProtoLogService)
+ Mockito.verify(mProtoLogConfigurationService)
.disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
}
@Test
public void handlesLogcatEnableCommandWithNoGroups() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "enable" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
@Test
public void handlesLogcatDisableCommandWithNoGroups() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "disable" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
similarity index 76%
rename from tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index feac59c..e1bdd77 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -67,7 +67,7 @@
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
-public class ProtoLogServiceTest {
+public class ProtoLogConfigurationServiceTest {
private static final String TEST_GROUP = "MY_TEST_GROUP";
private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP";
@@ -128,7 +128,7 @@
private File mViewerConfigFile;
- public ProtoLogServiceTest() throws IOException {
+ public ProtoLogConfigurationServiceTest() throws IOException {
}
@Before
@@ -150,10 +150,12 @@
@Test
public void canRegisterClientWithGroupsOnly() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -163,11 +165,13 @@
@Test
public void willDumpViewerConfigOnlyOnceOnTraceStop()
throws RemoteException, InvalidProtocolBufferException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true))
- .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
service.registerClient(mMockClient, args);
service.registerClient(mSecondMockClient, args);
@@ -196,14 +200,15 @@
@Test
public void willDumpViewerConfigOnLastClientDisconnected()
throws RemoteException, FileNotFoundException {
- final ProtoLogService.ViewerConfigFileTracer tracer =
- Mockito.mock(ProtoLogService.ViewerConfigFileTracer.class);
- final ProtoLogService service = new ProtoLogService(tracer);
+ final ProtoLogConfigurationService.ViewerConfigFileTracer tracer =
+ Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class);
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer);
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(
- TEST_GROUP, true))
- .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
service.registerClient(mMockClient, args);
service.registerClient(mSecondMockClient, args);
@@ -220,10 +225,11 @@
@Test
public void sendEnableLoggingToLogcatToClient() throws RemoteException {
- final var service = new ProtoLogService();
+ final var service = new ProtoLogConfigurationService();
- final var args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ final var args = new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -236,10 +242,12 @@
@Test
public void sendDisableLoggingToLogcatToClient() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -252,10 +260,12 @@
@Test
public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -267,14 +277,16 @@
@Test
public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
service.enableProtoLogToLogcat(TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
Mockito.verify(mMockClient).toggleLogcat(eq(true),
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
index 0b61948..9c44332 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -23,13 +23,21 @@
import java.util.zip.ZipFile
fun main(args: Array<String>) {
- if (args.size != 2) {
+ if (args.size < 2 || args.size > 3) {
usage()
}
val zipFileName = args[0]
val aidlFileName = args[1]
+ var stable = false
+ if (args.size == 3) {
+ if (args[2] != "--guarantee_stable") {
+ usage()
+ }
+ stable = true
+ }
+
val zipFile: ZipFile
try {
@@ -55,6 +63,9 @@
val outFile = File(aidlFileName)
val outWriter = outFile.bufferedWriter()
for (parcelable in parcelables) {
+ if (stable) {
+ outWriter.write("@JavaOnlyStableParcelable ")
+ }
outWriter.write("parcelable ")
outWriter.write(parcelable.replace('/', '.').replace('$', '.'))
outWriter.write(";\n")