Merge "Ignore InstallSessions with null appPackageName." 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/system-current.txt b/core/api/system-current.txt
index 9c93c3a..e40cbc1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3443,7 +3443,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.ActivityPolicyExemption> CREATOR;
}
- public static final class ActivityPolicyExemption.Builder {
+ @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final class ActivityPolicyExemption.Builder {
ctor public ActivityPolicyExemption.Builder();
method @NonNull public android.companion.virtual.ActivityPolicyExemption build();
method @NonNull public android.companion.virtual.ActivityPolicyExemption.Builder setComponentName(@NonNull android.content.ComponentName);
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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e57630b..68063c4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -233,6 +233,10 @@
private static final RateLimitingCache<List<RunningAppProcessInfo>> mRunningProcessesCache =
new RateLimitingCache<>(10, 4);
+ /** Rate-Limiting Cache that allows no more than 200 calls to the service per second. */
+ private static final RateLimitingCache<List<ProcessErrorStateInfo>> mErrorProcessesCache =
+ new RateLimitingCache<>(10, 2);
+
/**
* Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
* Will be called when a Uid has become frozen or unfrozen.
@@ -3685,6 +3689,16 @@
* specified.
*/
public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+ if (Flags.rateLimitGetProcessesInErrorState()) {
+ return mErrorProcessesCache.get(() -> {
+ return getProcessesInErrorStateInternal();
+ });
+ } else {
+ return getProcessesInErrorStateInternal();
+ }
+ }
+
+ private List<ProcessErrorStateInfo> getProcessesInErrorStateInternal() {
try {
return getService().getProcessesInErrorState();
} catch (RemoteException e) {
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index d9594d3..32e6e80 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -93,3 +93,14 @@
}
}
+flag {
+ namespace: "backstage_power"
+ name: "rate_limit_get_processes_in_error_state"
+ description: "Rate limit calls to getProcessesInErrorState using a cache"
+ is_fixed_read_only: true
+ bug: "361146083"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/core/java/android/companion/virtual/ActivityPolicyExemption.java b/core/java/android/companion/virtual/ActivityPolicyExemption.java
index c81bb43..dc285d4 100644
--- a/core/java/android/companion/virtual/ActivityPolicyExemption.java
+++ b/core/java/android/companion/virtual/ActivityPolicyExemption.java
@@ -118,6 +118,7 @@
/**
* Builder for {@link ActivityPolicyExemption}.
*/
+ @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
public static final class Builder {
private @Nullable ComponentName mComponentName;
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/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/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 91e9230..6568912 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_LEFT;
import static android.view.InsetsSource.SIDE_NONE;
+import static android.view.InsetsSource.SIDE_LEFT;
import static android.view.InsetsSource.SIDE_RIGHT;
import static android.view.InsetsSource.SIDE_TOP;
import static android.view.WindowInsets.Type.ime;
@@ -100,8 +100,6 @@
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;
@@ -122,8 +120,8 @@
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
- @InsetsType int types, InsetsAnimationControlCallbacks controller,
- InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
+ @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
+ Interpolator interpolator, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
mControls = controls;
@@ -157,10 +155,8 @@
}
mPendingInsets = mCurrentInsets;
- mDurationMs = insetsAnimationSpec.getDurationMs(mHasZeroInsetsIme);
- mInterpolator = insetsAnimationSpec.getInsetsInterpolator(mHasZeroInsetsIme);
-
- mAnimation = new WindowInsetsAnimation(mTypes, mInterpolator, mDurationMs);
+ mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
+ durationMs);
mAnimation.setAlpha(getCurrentAlpha());
mAnimationType = animationType;
mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
@@ -190,16 +186,6 @@
}
@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
deleted file mode 100644
index 7ad6661..0000000
--- a/core/java/android/view/InsetsAnimationSpec.java
+++ /dev/null
@@ -1,39 +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 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 fc185bc..1b3b3eb 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -33,6 +33,7 @@
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;
/**
@@ -109,15 +110,15 @@
@UiThread
public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
- @InsetsType int types, InsetsAnimationControlCallbacks controller,
- InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
+ @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
+ Interpolator interpolator, @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, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+ mCallbacks, durationMs, interpolator, 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 b1df51f..7896cbd 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, InsetsAnimationSpec {
+ implements WindowInsetsAnimationControlListener {
private WindowInsetsAnimationController mController;
private ValueAnimator mAnimator;
@@ -374,6 +374,7 @@
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;
@@ -387,6 +388,7 @@
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
mBehavior = behavior;
+ mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
mLoggingListener = loggingListener;
@@ -405,14 +407,13 @@
onAnimationFinish();
return;
}
- final boolean hasZeroInsetsIme = controller.hasZeroInsetsIme();
mAnimator = ValueAnimator.ofFloat(0f, 1f);
- mAnimator.setDuration(controller.getDurationMs());
+ mAnimator.setDuration(mDurationMs);
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 = hasZeroInsetsIme
+ hiddenInsets = controller.hasZeroInsetsIme()
? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right,
mFloatingImeBottomInset)
: hiddenInsets;
@@ -422,7 +423,7 @@
Insets end = mShow
? controller.getShownStateInsets()
: hiddenInsets;
- Interpolator insetsInterpolator = controller.getInsetsInterpolator();
+ Interpolator insetsInterpolator = getInsetsInterpolator();
Interpolator alphaInterpolator = getAlphaInterpolator();
mAnimator.addUpdateListener(animation -> {
float rawFraction = animation.getAnimatedFraction();
@@ -485,10 +486,9 @@
}
}
- @Override
- public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
+ protected Interpolator getInsetsInterpolator() {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks && hasZeroInsetsIme) {
+ if (mHasAnimationCallbacks) {
return SYNC_IME_INTERPOLATOR;
} else if (mShow) {
return LINEAR_OUT_SLOW_IN_INTERPOLATOR;
@@ -507,9 +507,10 @@
Interpolator getAlphaInterpolator() {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks && !mController.hasZeroInsetsIme()) {
+ if (mHasAnimationCallbacks) {
return input -> 1f;
} else if (mShow) {
+
// Alpha animation takes half the time with linear interpolation;
return input -> Math.min(1f, 2 * input);
} else {
@@ -533,10 +534,16 @@
if (DEBUG) Log.d(TAG, "onAnimationFinish showOnFinish: " + mShow);
}
- @Override
- public long getDurationMs(boolean hasZeroInsetsIme) {
+ /**
+ * To get the animation duration in MS.
+ */
+ public long getDurationMs() {
+ return mDurationMs;
+ }
+
+ private long calculateDurationMs() {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks && hasZeroInsetsIme) {
+ if (mHasAnimationCallbacks) {
return ANIMATION_DURATION_SYNC_IME_MS;
} else {
return ANIMATION_DURATION_UNSYNC_IME_MS;
@@ -586,13 +593,13 @@
private static class PendingControlRequest {
PendingControlRequest(@InsetsType int types, WindowInsetsAnimationControlListener listener,
- InsetsAnimationSpec insetsAnimationSpec,
- @AnimationType int animationType,
+ long durationMs, Interpolator interpolator, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CancellationSignal cancellationSignal, boolean useInsetsAnimationThread) {
this.types = types;
this.listener = listener;
- this.mInsetsAnimationSpec = insetsAnimationSpec;
+ this.durationMs = durationMs;
+ this.interpolator = interpolator;
this.animationType = animationType;
this.layoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
this.cancellationSignal = cancellationSignal;
@@ -601,7 +608,8 @@
@InsetsType int types;
final WindowInsetsAnimationControlListener listener;
- final InsetsAnimationSpec mInsetsAnimationSpec;
+ final long durationMs;
+ final Interpolator interpolator;
final @AnimationType int animationType;
final @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation;
final CancellationSignal cancellationSignal;
@@ -1193,10 +1201,12 @@
// 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.mInsetsAnimationSpec,
- pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation,
+ controlAnimationUnchecked(
+ pendingRequest.types, pendingRequest.cancellationSignal,
+ pendingRequest.listener, null /* frame */,
+ true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
+ pendingRequest.animationType,
+ pendingRequest.layoutInsetsDuringAnimation,
pendingRequest.useInsetsAnimationThread, statsToken);
}
@@ -1317,26 +1327,18 @@
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, spec,
- animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
+ controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
+ interpolator, animationType,
+ getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
false /* useInsetsAnimationThread */, null);
}
private void controlAnimationUnchecked(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
- InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
+ long durationMs, Interpolator interpolator,
+ @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -1347,7 +1349,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,
- insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+ durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
useInsetsAnimationThread, statsToken);
// We are finishing setting the requested visible types. Report them to the server
@@ -1358,7 +1360,8 @@
private void controlAnimationUncheckedInner(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
- InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
+ long durationMs, Interpolator interpolator,
+ @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
if ((types & mTypesBeingCancelled) != 0) {
@@ -1415,8 +1418,8 @@
// TODO (b/323319146) remove layoutInsetsDuringAnimation from
// PendingControlRequest, as it is now only used for showing
final PendingControlRequest request = new PendingControlRequest(types,
- listener, insetsAnimationSpec, animationType,
- LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+ listener, durationMs,
+ interpolator, animationType, LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
cancellationSignal, false /* useInsetsAnimationThread */);
mPendingImeControlRequest = request;
// only add a timeout when the control is not currently showing
@@ -1457,9 +1460,11 @@
if (!imeReady) {
// IME isn't ready, all requested types will be animated once IME is ready
abortPendingImeControlRequest();
- final PendingControlRequest request = new PendingControlRequest(types, listener,
- insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
- cancellationSignal, useInsetsAnimationThread);
+ final PendingControlRequest request = new PendingControlRequest(types,
+ listener, durationMs,
+ interpolator, animationType, layoutInsetsDuringAnimation,
+ cancellationSignal,
+ useInsetsAnimationThread);
mPendingImeControlRequest = request;
mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS);
if (DEBUG) Log.d(TAG, "Ime not ready. Create pending request");
@@ -1515,11 +1520,11 @@
final InsetsAnimationControlRunner runner = useInsetsAnimationThread
? new InsetsAnimationThreadControlRunner(controls,
- frame, mState, listener, typesReady, this,
- insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
- mHost.getTranslator(), mHost.getHandler(), statsToken)
+ frame, mState, listener, typesReady, this, durationMs, interpolator,
+ animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
+ mHost.getHandler(), statsToken)
: new InsetsAnimationControlImpl(controls,
- frame, mState, listener, typesReady, this, insetsAnimationSpec,
+ frame, mState, listener, typesReady, this, durationMs, interpolator,
animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
statsToken);
if ((typesReady & WindowInsets.Type.ime()) != 0) {
@@ -2018,7 +2023,7 @@
// the controlled types should be animated regardless of the frame.
controlAnimationUnchecked(
types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
- listener /* insetsAnimationSpec */,
+ listener.getDurationMs(), listener.getInsetsInterpolator(),
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 f90b841..6e62221 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -233,16 +233,6 @@
}
@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 d3ea982..6578e9b 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -23,7 +23,6 @@
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.
@@ -189,16 +188,4 @@
* 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/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/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/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/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/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 786f1e8..668487d 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -40,7 +40,6 @@
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;
@@ -118,21 +117,11 @@
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, spec /* insetsAnimationSpecCreator */, 0 /* animationType */,
- 0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */);
+ mMockController, 10 /* durationMs */, new LinearInterpolator(),
+ 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+ null /* statsToken */);
mController.setReadyDispatched(true);
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 7bc0d2f..ce7e858 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -69,6 +69,7 @@
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
@@ -136,7 +137,7 @@
mTestHandler = new TestHandler(null, mTestClock);
mTestHost = spy(new TestHost(mViewRoot));
mController = new InsetsController(mTestHost, (controller, id, type) -> {
- if (type == ime()) {
+ if (!Flags.refactorInsetsController() && type == ime()) {
return new InsetsSourceConsumer(id, type, controller.getState(),
Transaction::new, controller) {
@@ -260,7 +261,11 @@
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
// When using the animation thread, this must not invoke onReady()
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
});
@@ -277,7 +282,12 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
mController.show(all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
@@ -299,7 +309,11 @@
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations();
assertTrue(isRequestedVisible(mController, ime()));
mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty());
@@ -469,7 +483,12 @@
assertFalse(mController.getState().peekSource(ID_IME).isVisible());
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
// Gaining control shortly after
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
@@ -493,7 +512,12 @@
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
@@ -558,7 +582,13 @@
@Test
public void testControlImeNotReady() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -571,7 +601,13 @@
verify(listener, never()).onReady(any(), anyInt());
// Pretend that IME is calling.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ // Send the IME control with leash, so that the animation can start
+ InsetsSourceControl ime = createControl(ID_IME, ime(), true /* hasLeash */);
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -583,7 +619,13 @@
@Test
public void testControlImeNotReady_controlRevoked() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -604,7 +646,13 @@
@Test
public void testControlImeNotReady_timeout() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -655,7 +703,11 @@
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
@@ -886,7 +938,11 @@
// Showing invisible ime should only causes insets change once.
clearInvocations(mTestHost);
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
verify(mTestHost, times(1)).notifyInsetsChanged();
// Sending the same insets state should not cause insets change.
@@ -953,7 +1009,11 @@
assertNull(imeInsetsConsumer.getControl());
// Verify IME requested visibility should be updated to IME consumer from controller.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
assertTrue(isRequestedVisible(mController, ime()));
mController.hide(ime());
@@ -966,7 +1026,11 @@
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -990,8 +1054,13 @@
public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() {
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1008,12 +1077,20 @@
assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
// verify show request is ignored during pre commit phase of predictive back anim
- mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ } else {
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
// verify show request is applied during post commit phase of predictive back anim
mController.setPredictiveBackImeHideAnimInProgress(true);
- mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ } else {
+ mController.show(ime(), false /* fromIme */, null /* statsToken */);
+ }
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
// additionally verify that IME ends up visible
@@ -1027,7 +1104,11 @@
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1058,11 +1139,15 @@
}
private InsetsSourceControl createControl(int id, @InsetsType int type) {
+ return createControl(id, type, true);
+ }
+
+ private InsetsSourceControl createControl(int id, @InsetsType int type, boolean hasLeash) {
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
- return new InsetsSourceControl(id, type, copy,
+ return new InsetsSourceControl(id, type, hasLeash ? copy : null,
(type & WindowInsets.Type.defaultVisible()) != 0, new Point(), Insets.NONE);
}
@@ -1071,9 +1156,13 @@
}
private InsetsSourceControl[] prepareControls() {
+ return prepareControls(true);
+ }
+
+ private InsetsSourceControl[] prepareControls(boolean imeControlHasLeash) {
final InsetsSourceControl navBar = createControl(ID_NAVIGATION_BAR, navigationBars());
final InsetsSourceControl statusBar = createControl(ID_STATUS_BAR, statusBars());
- final InsetsSourceControl ime = createControl(ID_IME, ime());
+ final InsetsSourceControl ime = createControl(ID_IME, ime(), imeControlHasLeash);
InsetsSourceControl[] controls = new InsetsSourceControl[3];
controls[0] = navBar;
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/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1a3aa8e..b338a2a 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,9 +46,6 @@
srcs: [
"src/com/android/wm/shell/common/bubbles/*.kt",
"src/com/android/wm/shell/common/bubbles/*.java",
- "src/com/android/wm/shell/common/desktopmode/*.kt",
- "src/com/android/wm/shell/pip/PipContentOverlay.java",
- "src/com/android/wm/shell/util/**/*.java",
],
path: "src",
}
@@ -208,6 +205,7 @@
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
":wm_shell-aidls",
+ ":wm_shell-shared-aidls",
],
resource_dirs: [
"res",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
similarity index 86%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
index c968e80..e21bf8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared;
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
index a2d2b9a..65e079e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -25,6 +25,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.wm.shell.shared.split.SplitBounds;
+
import java.util.Arrays;
import java.util.List;
import java.util.Set;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
index c968e80..f7ddf71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared.desktopmode;
parcelable DesktopModeTransitionSource;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
index dbbf1786..d15fbed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode
+package com.android.wm.shell.shared.desktopmode
import android.os.Parcel
import android.os.Parcelable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index ff2d46e..cf39415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.shared.pip;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
index 88b7528..7c1faa66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared.split;
import android.graphics.Rect;
import android.os.Parcel;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3dc33c2..b508c1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1225,7 +1225,7 @@
mBubblePositioner.setBubbleBarLocation(location);
mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
if (mBubbleData.getSelectedBubble() != null) {
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ showExpandedViewForBubbleBar();
}
}
@@ -1243,7 +1243,7 @@
}
if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
// We did not remove the selected bubble. Expand it again
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ showExpandedViewForBubbleBar();
}
}
@@ -1997,15 +1997,10 @@
@Override
public void expansionChanged(boolean isExpanded) {
- if (mLayerView != null) {
- if (!isExpanded) {
- mLayerView.collapse();
- } else {
- BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
- if (selectedBubble != null) {
- mLayerView.showExpandedView(selectedBubble);
- }
- }
+ // in bubble bar mode, let the request to show the expanded view come from launcher.
+ // only collapse here if we're collapsing.
+ if (mLayerView != null && !isExpanded) {
+ mLayerView.collapse();
}
}
@@ -2151,6 +2146,13 @@
}
};
+ private void showExpandedViewForBubbleBar() {
+ BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+ if (selectedBubble != null && mLayerView != null) {
+ mLayerView.showExpandedView(selectedBubble);
+ }
+ }
+
private void updateOverflowButtonDot() {
BubbleOverflow overflow = mBubbleData.getOverflow();
if (overflow == null) return;
@@ -2532,6 +2534,15 @@
if (mLayerView != null) mLayerView.updateExpandedView();
});
}
+
+ @Override
+ public void showExpandedView() {
+ mMainExecutor.execute(() -> {
+ if (mLayerView != null) {
+ showExpandedViewForBubbleBar();
+ }
+ });
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5c78974..5779a8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -53,4 +53,6 @@
oneway void showShortcutBubble(in ShortcutInfo info) = 12;
oneway void showAppBubble(in Intent intent) = 13;
+
+ oneway void showExpandedView() = 14;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 31c8f1e..cca7500 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,8 +18,8 @@
import android.graphics.Region;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index eca3c1f..dba8c93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.desktopmode
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.sysui.ShellCommandHandler
import java.io.PrintWriter
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
index b24bd10..d6fccd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.desktopmode
import android.view.WindowManager.TransitionType
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TYPES
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index ffd534b..2852631 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -69,7 +69,6 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -89,6 +88,7 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreenController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 04506c1..80e106f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -41,7 +41,7 @@
import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 171378f..e87be52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -44,7 +44,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index a7ec203..b036e40e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -18,8 +18,8 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.window.RemoteTransition;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
/**
* Interface that is exposed to remote callers to manipulate desktop mode features.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b0c896f..4df649c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -43,6 +43,7 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.transition.Transitions;
import java.lang.annotation.Retention;
@@ -418,7 +419,7 @@
}
SurfaceControl getContentOverlayLeash() {
- return mContentOverlay == null ? null : mContentOverlay.mLeash;
+ return mContentOverlay == null ? null : mContentOverlay.getLeash();
}
void setColorContentOverlay(Context context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 2de545a..e4cd10f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -91,6 +91,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -361,8 +362,7 @@
SurfaceControl mPipOverlay;
/**
- * The app bounds used for the buffer size of the
- * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+ * The app bounds used for the buffer size of the {@link PipContentOverlay.PipAppIconOverlay}.
*
* Note that this is empty if the overlay is removed or if it's some other type of overlay
* defined in {@link PipContentOverlay}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index b102e40..05d1984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -75,6 +75,7 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
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 ed18712..c6b1a72 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
@@ -50,10 +50,10 @@
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipContentOverlay;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 4048c5b..ebfd357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@
import android.view.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
/**
* Interface that is exposed to remote callers to fetch recent tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 77b8663..8c5d1e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,8 +19,8 @@
import android.annotation.Nullable;
import android.graphics.Color;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.util.List;
import java.util.concurrent.Executor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 2f0af855..39bea1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -51,16 +51,16 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 7a9eb1c..c90da05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -30,7 +30,7 @@
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
-import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0b5c751..95f864a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -133,13 +133,13 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.SplitBounds;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
deleted file mode 100644
index 15797cd..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 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.util;
-
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
deleted file mode 100644
index 482aaab..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file KtProtolog.kt = file:platform/development:/tools/winscope/OWNERS
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 20a406f..1f95667 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -97,7 +97,6 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -107,6 +106,7 @@
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 8e87d0f..2bec3fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -26,7 +26,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
-import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -79,10 +79,10 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 114c331..deef378 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -318,7 +318,7 @@
rootView.setOnTouchListener { _, event ->
if (event.actionMasked == ACTION_OUTSIDE) {
onOutsideTouchListener?.invoke()
- false
+ return@setOnTouchListener false
}
true
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
index 518c00d..db4e93d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
@@ -18,11 +18,6 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.TASK_DRAG
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
@@ -33,6 +28,11 @@
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 058a26a..d248720 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -83,7 +83,6 @@
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
@@ -94,6 +93,7 @@
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e5157c9..e0463b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -48,7 +48,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 6736593..0c3f98a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -24,13 +24,13 @@
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e1fe4e9..a8d40db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -68,13 +68,13 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
import org.junit.After;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index bfb760b..248393c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -12,7 +12,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.shared.split.SplitBounds;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index f7ac3e4..da0aca7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -79,12 +79,12 @@
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ac75c07..c1c30f5 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -49,6 +49,15 @@
#endif // __ANDROID__
}
+inline bool typeface_redesign() {
+#ifdef __ANDROID__
+ static bool flag = com_android_text_flags_typeface_redesign();
+ return flag;
+#else
+ return true;
+#endif // __ANDROID__
+}
+
} // namespace text_feature
} // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index f8574ee..1510ce1 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -27,6 +27,8 @@
#include <cutils/compiler.h>
#include <log/log.h>
#include <minikin/Layout.h>
+
+#include "FeatureFlags.h"
#include "MinikinSkia.h"
#include "Paint.h"
#include "Typeface.h"
@@ -71,27 +73,42 @@
static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
float saveSkewX = paint->getSkFont().getSkewX();
bool savefakeBold = paint->getSkFont().isEmbolden();
- const minikin::MinikinFont* curFont = nullptr;
- size_t start = 0;
- size_t nGlyphs = layout.nGlyphs();
- for (size_t i = 0; i < nGlyphs; i++) {
- const minikin::MinikinFont* nextFont = layout.typeface(i).get();
- if (i > 0 && nextFont != curFont) {
+ if (text_feature::typeface_redesign()) {
+ for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
+ uint32_t start = layout.getFontRunStart(runIdx);
+ uint32_t end = layout.getFontRunEnd(runIdx);
+ const minikin::FakedFont& fakedFont = layout.getFontRunFont(runIdx);
+
+ std::shared_ptr<minikin::MinikinFont> font = fakedFont.typeface();
SkFont* skfont = &paint->getSkFont();
- MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
- f(start, i);
+ MinikinFontSkia::populateSkFont(skfont, font.get(), fakedFont.fakery);
+ f(start, end);
skfont->setSkewX(saveSkewX);
skfont->setEmbolden(savefakeBold);
- start = i;
}
- curFont = nextFont;
- }
- if (nGlyphs > start) {
- SkFont* skfont = &paint->getSkFont();
- MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
- f(start, nGlyphs);
- skfont->setSkewX(saveSkewX);
- skfont->setEmbolden(savefakeBold);
+ } else {
+ const minikin::MinikinFont* curFont = nullptr;
+ size_t start = 0;
+ size_t nGlyphs = layout.nGlyphs();
+ for (size_t i = 0; i < nGlyphs; i++) {
+ const minikin::MinikinFont* nextFont = layout.typeface(i).get();
+ if (i > 0 && nextFont != curFont) {
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+ f(start, i);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
+ start = i;
+ }
+ curFont = nextFont;
+ }
+ if (nGlyphs > start) {
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+ f(start, nGlyphs);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
+ }
}
}
};
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/EasterEgg/src/com/android/egg/paint/PaintActivity.java b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
index ac47fbd..391b16d 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
+++ b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
@@ -23,7 +23,6 @@
import android.app.Activity;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
@@ -38,9 +37,7 @@
import com.android.egg.R;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.stream.IntStream;
public class PaintActivity extends Activity {
@@ -60,31 +57,28 @@
private View.OnClickListener buttonHandler = new View.OnClickListener() {
@Override
public void onClick(View view) {
- switch (view.getId()) {
- case R.id.btnBrush:
- view.setSelected(true);
- hideToolbar(colors);
- toggleToolbar(brushes);
- break;
- case R.id.btnColor:
- view.setSelected(true);
- hideToolbar(brushes);
- toggleToolbar(colors);
- break;
- case R.id.btnClear:
- painting.clear();
- break;
- case R.id.btnSample:
- sampling = true;
- view.setSelected(true);
- break;
- case R.id.btnZen:
- painting.setZenMode(!painting.getZenMode());
- view.animate()
- .setStartDelay(200)
- .setInterpolator(new OvershootInterpolator())
- .rotation(painting.getZenMode() ? 0f : 90f);
- break;
+ // With non final fields in the R class we can't switch on the
+ // id since the case values are no longer constants.
+ int viewId = view.getId();
+ if (viewId == R.id.btnBrush) {
+ view.setSelected(true);
+ hideToolbar(colors);
+ toggleToolbar(brushes);
+ } else if (viewId == R.id.btnColor) {
+ view.setSelected(true);
+ hideToolbar(brushes);
+ toggleToolbar(colors);
+ } else if (viewId == R.id.btnClear) {
+ painting.clear();
+ } else if (viewId == R.id.btnSample) {
+ sampling = true;
+ view.setSelected(true);
+ } else if (viewId == R.id.btnZen) {
+ painting.setZenMode(!painting.getZenMode());
+ view.animate()
+ .setStartDelay(200)
+ .setInterpolator(new OvershootInterpolator())
+ .rotation(painting.getZenMode() ? 0f : 90f);
}
}
};
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/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 d0fb279..a129ac1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1384,3 +1384,13 @@
}
}
+flag {
+ name: "media_load_metadata_via_media_data_loader"
+ namespace: "systemui"
+ description: "Use MediaDataLoader for loading media metadata with better threading"
+ bug: "358350077"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
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/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/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index 2bc8c87..b166737 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -26,15 +26,15 @@
import kotlinx.coroutines.launch
internal fun CoroutineScope.animateContent(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
transition: TransitionState.Transition,
oneOffAnimation: OneOffAnimation,
targetProgress: Float,
- startTransition: () -> Unit,
- finishTransition: () -> Unit,
+ chain: Boolean = true,
) {
// Start the transition. This will compute the TransformationSpec associated to [transition],
// which we need to initialize the Animatable that will actually animate it.
- startTransition()
+ layoutState.startTransition(transition, chain)
// The transition now contains the transformation spec that we should use to instantiate the
// Animatable.
@@ -59,7 +59,7 @@
try {
animatable.animateTo(targetProgress, animationSpec, initialVelocity)
} finally {
- finishTransition()
+ layoutState.finishTransition(transition)
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
new file mode 100644
index 0000000..e020f14
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.compose.animation.scene
+
+import com.android.compose.animation.scene.content.state.TransitionState
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+
+/** Trigger a one-off transition to show or hide an overlay. */
+internal fun CoroutineScope.showOrHideOverlay(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ overlay: OverlayKey,
+ fromOrToScene: SceneKey,
+ isShowing: Boolean,
+ transitionKey: TransitionKey?,
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay?,
+ reversed: Boolean,
+): TransitionState.Transition.ShowOrHideOverlay {
+ val targetProgress = if (reversed) 0f else 1f
+ val (fromContent, toContent) =
+ if (isShowing xor reversed) {
+ fromOrToScene to overlay
+ } else {
+ overlay to fromOrToScene
+ }
+
+ val oneOffAnimation = OneOffAnimation()
+ val transition =
+ OneOffShowOrHideOverlayTransition(
+ overlay = overlay,
+ fromOrToScene = fromOrToScene,
+ fromContent = fromContent,
+ toContent = toContent,
+ isEffectivelyShown = isShowing,
+ key = transitionKey,
+ replacedTransition = replacedTransition,
+ oneOffAnimation = oneOffAnimation,
+ )
+
+ animateContent(
+ layoutState = layoutState,
+ transition = transition,
+ oneOffAnimation = oneOffAnimation,
+ targetProgress = targetProgress,
+ )
+
+ return transition
+}
+
+/** Trigger a one-off transition to replace an overlay by another one. */
+internal fun CoroutineScope.replaceOverlay(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ fromOverlay: OverlayKey,
+ toOverlay: OverlayKey,
+ transitionKey: TransitionKey?,
+ replacedTransition: TransitionState.Transition.ReplaceOverlay?,
+ reversed: Boolean,
+): TransitionState.Transition.ReplaceOverlay {
+ val targetProgress = if (reversed) 0f else 1f
+ val effectivelyShownOverlay = if (reversed) fromOverlay else toOverlay
+
+ val oneOffAnimation = OneOffAnimation()
+ val transition =
+ OneOffOverlayReplacingTransition(
+ fromOverlay = fromOverlay,
+ toOverlay = toOverlay,
+ effectivelyShownOverlay = effectivelyShownOverlay,
+ key = transitionKey,
+ replacedTransition = replacedTransition,
+ oneOffAnimation = oneOffAnimation,
+ )
+
+ animateContent(
+ layoutState = layoutState,
+ transition = transition,
+ oneOffAnimation = oneOffAnimation,
+ targetProgress = targetProgress,
+ )
+
+ return transition
+}
+
+private class OneOffShowOrHideOverlayTransition(
+ overlay: OverlayKey,
+ fromOrToScene: SceneKey,
+ fromContent: ContentKey,
+ toContent: ContentKey,
+ override val isEffectivelyShown: Boolean,
+ override val key: TransitionKey?,
+ replacedTransition: TransitionState.Transition?,
+ private val oneOffAnimation: OneOffAnimation,
+) :
+ TransitionState.Transition.ShowOrHideOverlay(
+ overlay,
+ fromOrToScene,
+ fromContent,
+ toContent,
+ replacedTransition,
+ ) {
+ override val progress: Float
+ get() = oneOffAnimation.progress
+
+ override val progressVelocity: Float
+ get() = oneOffAnimation.progressVelocity
+
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+
+ override fun finish(): Job = oneOffAnimation.finish()
+}
+
+private class OneOffOverlayReplacingTransition(
+ fromOverlay: OverlayKey,
+ toOverlay: OverlayKey,
+ override val effectivelyShownOverlay: OverlayKey,
+ override val key: TransitionKey?,
+ replacedTransition: TransitionState.Transition?,
+ private val oneOffAnimation: OneOffAnimation,
+) : TransitionState.Transition.ReplaceOverlay(fromOverlay, toOverlay, replacedTransition) {
+ override val progress: Float
+ get() = oneOffAnimation.progress
+
+ override val progressVelocity: Float
+ get() = oneOffAnimation.progressVelocity
+
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+
+ override fun finish(): Job = oneOffAnimation.finish()
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 7eef5d6..4aa50b5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -412,7 +412,7 @@
if (canOverflow) transition.progress
else transition.progress.fastCoerceIn(0f, 1f)
}
- overscrollSpec.scene == transition.toContent -> 1f
+ overscrollSpec.content == transition.toContent -> 1f
else -> 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 8aa0690..abe079a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -166,11 +166,11 @@
}
animateContent(
+ layoutState = layoutState,
transition = transition,
oneOffAnimation = oneOffAnimation,
targetProgress = targetProgress,
- startTransition = { layoutState.startTransition(transition, chain) },
- finishTransition = { layoutState.finishTransition(transition) },
+ chain = chain,
)
return transition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 6ea0285..9b1740d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -310,9 +310,10 @@
val transition = elementState as? TransitionState.Transition
- // If this element is not supposed to be laid out now because the other content of its
- // transition is overscrolling, then lay out the element normally and don't place it.
- val overscrollScene = transition?.currentOverscrollSpec?.scene
+ // If this element is not supposed to be laid out now, either because it is not part of any
+ // ongoing transition or the other content of its transition is overscrolling, then lay out
+ // the element normally and don't place it.
+ val overscrollScene = transition?.currentOverscrollSpec?.content
val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
if (isOtherSceneOverscrolling) {
return doNotPlace(measurable, constraints)
@@ -845,7 +846,7 @@
transition: TransitionState.Transition,
): Boolean {
// If we are overscrolling, only place/compose the element in the overscrolling scene.
- val overscrollScene = transition.currentOverscrollSpec?.scene
+ val overscrollScene = transition.currentOverscrollSpec?.content
if (overscrollScene != null) {
return content == overscrollScene
}
@@ -1184,7 +1185,7 @@
val currentContent = currentContentState.content
if (transition is TransitionState.HasOverscrollProperties) {
val overscroll = transition.currentOverscrollSpec
- if (overscroll?.scene == currentContent) {
+ if (overscroll?.content == currentContent) {
val elementSpec =
overscroll.transformationSpec.transformations(element.key, currentContent)
val propertySpec = transformation(elementSpec) ?: return currentValue()
@@ -1210,7 +1211,7 @@
// TODO(b/290184746): Make sure that we don't overflow transformations associated to a
// range.
val directionSign = if (transition.isUpOrLeft) -1 else 1
- val isToContent = overscroll.scene == transition.toContent
+ val isToContent = overscroll.content == transition.toContent
val linearProgress = transition.progress.let { if (isToContent) it - 1f else it }
val progressConverter =
overscroll.progressConverter
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 21f11e4..5f5141e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -432,7 +432,7 @@
val progress =
when {
overscrollSpec == null -> transition.progress
- overscrollSpec.scene == transition.toScene -> 1f
+ overscrollSpec.content == transition.toScene -> 1f
else -> 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 74cd136..0ac6912 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -81,12 +81,15 @@
/**
* Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
- * the scenes we are animating from and/or to.
+ * the contents we are animating from and/or to.
*/
- fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean
+ fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean
- /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
- fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
+ /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
+ fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean
+
+ /** Whether we are transitioning from or to [content]. */
+ fun isTransitioningFromOrTo(content: ContentKey): Boolean
}
/** A [SceneTransitionLayoutState] whose target scene can be imperatively set. */
@@ -260,14 +263,19 @@
}
}
- override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
+ override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
val transition = currentTransition ?: return false
return transition.isTransitioning(from, to)
}
- override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+ override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
val transition = currentTransition ?: return false
- return transition.isTransitioningBetween(scene, other)
+ return transition.isTransitioningBetween(content, other)
+ }
+
+ override fun isTransitioningFromOrTo(content: ContentKey): Boolean {
+ val transition = currentTransition ?: return false
+ return transition.isTransitioningFromOrTo(content)
}
override fun setTargetScene(
@@ -293,10 +301,7 @@
*
* Important: you *must* call [finishTransition] once the transition is finished.
*/
- internal fun startTransition(
- transition: TransitionState.Transition.ChangeCurrentScene,
- chain: Boolean = true,
- ) {
+ internal fun startTransition(transition: TransitionState.Transition, chain: Boolean = true) {
checkThread()
// Set the current scene and overlays on the transition.
@@ -305,23 +310,23 @@
transition.currentOverlaysWhenTransitionStarted = currentState.currentOverlays
// Compute the [TransformationSpec] when the transition starts.
- val fromScene = transition.fromScene
- val toScene = transition.toScene
+ val fromContent = transition.fromContent
+ val toContent = transition.toContent
val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
// Update the transition specs.
transition.transformationSpec =
transitions
- .transitionSpec(fromScene, toScene, key = transition.key)
+ .transitionSpec(fromContent, toContent, key = transition.key)
.transformationSpec()
transition.previewTransformationSpec =
transitions
- .transitionSpec(fromScene, toScene, key = transition.key)
+ .transitionSpec(fromContent, toContent, key = transition.key)
.previewTransformationSpec()
if (orientation != null) {
transition.updateOverscrollSpecs(
- fromSpec = transitions.overscrollSpec(fromScene, orientation),
- toSpec = transitions.overscrollSpec(toScene, orientation),
+ fromSpec = transitions.overscrollSpec(fromContent, orientation),
+ toSpec = transitions.overscrollSpec(toContent, orientation),
)
} else {
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
@@ -402,32 +407,27 @@
}
private fun setupTransitionLinks(transition: TransitionState.Transition) {
- when (transition) {
- is TransitionState.Transition.ChangeCurrentScene -> {
- stateLinks.fastForEach { stateLink ->
- val matchingLinks =
- stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
- if (matchingLinks.isEmpty()) return@fastForEach
- if (matchingLinks.size > 1) error("More than one link matched.")
+ stateLinks.fastForEach { stateLink ->
+ val matchingLinks =
+ stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
+ if (matchingLinks.isEmpty()) return@fastForEach
+ if (matchingLinks.size > 1) error("More than one link matched.")
- val targetCurrentScene = stateLink.target.transitionState.currentScene
- val matchingLink = matchingLinks[0]
+ val targetCurrentScene = stateLink.target.transitionState.currentScene
+ val matchingLink = matchingLinks[0]
- if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+ if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
- val linkedTransition =
- LinkedTransition(
- originalTransition = transition,
- fromScene = targetCurrentScene,
- toScene = matchingLink.targetTo,
- key = matchingLink.targetTransitionKey,
- )
+ val linkedTransition =
+ LinkedTransition(
+ originalTransition = transition,
+ fromScene = targetCurrentScene,
+ toScene = matchingLink.targetTo,
+ key = matchingLink.targetTransitionKey,
+ )
- stateLink.target.startTransition(linkedTransition)
- transition.activeTransitionLinks[stateLink] = linkedTransition
- }
- }
- else -> error("transition links are not supported with overlays yet")
+ stateLink.target.startTransition(linkedTransition)
+ transition.activeTransitionLinks[stateLink] = linkedTransition
}
}
@@ -552,12 +552,39 @@
checkThread()
// Overlay is already shown, do nothing.
- if (overlay in transitionState.currentOverlays) {
+ val currentState = transitionState
+ if (overlay in currentState.currentOverlays) {
return
}
- // TODO(b/353679003): Animate the overlay instead of instantly snapping to an Idle state.
- snapToScene(transitionState.currentScene, transitionState.currentOverlays + overlay)
+ val fromScene = currentState.currentScene
+ fun animate(
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.showOrHideOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ overlay = overlay,
+ fromOrToScene = fromScene,
+ isShowing = true,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (
+ currentState is TransitionState.Transition.ShowOrHideOverlay &&
+ currentState.overlay == overlay &&
+ currentState.fromOrToScene == fromScene
+ ) {
+ animate(
+ replacedTransition = currentState,
+ reversed = overlay == currentState.fromContent
+ )
+ } else {
+ animate()
+ }
}
override fun hideOverlay(
@@ -568,12 +595,36 @@
checkThread()
// Overlay is not shown, do nothing.
- if (!transitionState.currentOverlays.contains(overlay)) {
+ val currentState = transitionState
+ if (!currentState.currentOverlays.contains(overlay)) {
return
}
- // TODO(b/353679003): Animate the overlay instead of instantly snapping to an Idle state.
- snapToScene(transitionState.currentScene, transitionState.currentOverlays - overlay)
+ val toScene = currentState.currentScene
+ fun animate(
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.showOrHideOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ overlay = overlay,
+ fromOrToScene = toScene,
+ isShowing = false,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (
+ currentState is TransitionState.Transition.ShowOrHideOverlay &&
+ currentState.overlay == overlay &&
+ currentState.fromOrToScene == toScene
+ ) {
+ animate(replacedTransition = currentState, reversed = overlay == currentState.toContent)
+ } else {
+ animate()
+ }
}
override fun replaceOverlay(
@@ -583,16 +634,45 @@
transitionKey: TransitionKey?
) {
checkThread()
- require(from in currentOverlays) {
+
+ val currentState = transitionState
+ require(from != to) {
+ "replaceOverlay must be called with different overlays (from = to = ${from.debugName})"
+ }
+ require(from in currentState.currentOverlays) {
"Overlay ${from.debugName} is not shown so it can't be replaced by ${to.debugName}"
}
- require(to !in currentOverlays) {
+ require(to !in currentState.currentOverlays) {
"Overlay ${to.debugName} is already shown so it can't replace ${from.debugName}"
}
- // TODO(b/353679003): Animate from into to instead of hiding/showing the overlays
- // separately.
- snapToScene(transitionState.currentScene, transitionState.currentOverlays - from + to)
+ fun animate(
+ replacedTransition: TransitionState.Transition.ReplaceOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.replaceOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ fromOverlay = if (reversed) to else from,
+ toOverlay = if (reversed) from else to,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (currentState is TransitionState.Transition.ReplaceOverlay) {
+ if (currentState.fromOverlay == from && currentState.toOverlay == to) {
+ animate(replacedTransition = currentState, reversed = false)
+ return
+ }
+
+ if (currentState.fromOverlay == to && currentState.toOverlay == from) {
+ animate(replacedTransition = currentState, reversed = true)
+ return
+ }
+ }
+
+ animate()
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d35d956..cefcff7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -49,16 +49,16 @@
) {
private val transitionCache =
mutableMapOf<
- SceneKey,
- MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+ ContentKey,
+ MutableMap<ContentKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
>()
private val overscrollCache =
- mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
+ mutableMapOf<ContentKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
internal fun transitionSpec(
- from: SceneKey,
- to: SceneKey,
+ from: ContentKey,
+ to: ContentKey,
key: TransitionKey?,
): TransitionSpecImpl {
return transitionCache
@@ -67,7 +67,11 @@
.getOrPut(key) { findSpec(from, to, key) }
}
- private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+ private fun findSpec(
+ from: ContentKey,
+ to: ContentKey,
+ key: TransitionKey?
+ ): TransitionSpecImpl {
val spec = transition(from, to, key) { it.from == from && it.to == to }
if (spec != null) {
return spec
@@ -93,8 +97,8 @@
}
private fun transition(
- from: SceneKey,
- to: SceneKey,
+ from: ContentKey,
+ to: ContentKey,
key: TransitionKey?,
filter: (TransitionSpecImpl) -> Boolean,
): TransitionSpecImpl? {
@@ -110,16 +114,16 @@
return match
}
- private fun defaultTransition(from: SceneKey, to: SceneKey) =
+ private fun defaultTransition(from: ContentKey, to: ContentKey) =
TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)
- internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
+ internal fun overscrollSpec(scene: ContentKey, orientation: Orientation): OverscrollSpecImpl? =
overscrollCache
.getOrPut(scene) { mutableMapOf() }
- .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } }
+ .getOrPut(orientation) { overscroll(scene, orientation) { it.content == scene } }
private fun overscroll(
- scene: SceneKey,
+ scene: ContentKey,
orientation: Orientation,
filter: (OverscrollSpecImpl) -> Boolean,
): OverscrollSpecImpl? {
@@ -264,10 +268,10 @@
previewTransformationSpec?.invoke()
}
-/** The definition of the overscroll behavior of the [scene]. */
+/** The definition of the overscroll behavior of the [content]. */
interface OverscrollSpec {
/** The scene we are over scrolling. */
- val scene: SceneKey
+ val content: ContentKey
/** The orientation of this [OverscrollSpec]. */
val orientation: Orientation
@@ -288,7 +292,7 @@
}
internal class OverscrollSpecImpl(
- override val scene: SceneKey,
+ override val content: ContentKey,
override val orientation: Orientation,
override val transformationSpec: TransformationSpecImpl,
override val progressConverter: ProgressConverter?,
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/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 523e5bdd7..18e356f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -107,7 +107,7 @@
): OverscrollSpec {
val spec =
OverscrollSpecImpl(
- scene = scene,
+ content = scene,
orientation = orientation,
transformationSpec =
TransformationSpecImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 6f608cb..6bc1754 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -96,6 +96,8 @@
.approachLayout(
isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
) { measurable, constraints ->
+ // TODO(b/353679003): Use the ModifierNode API to set this *before* the approach
+ // pass is started.
targetSize = lookaheadSize
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 89b0040..59ddb13 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -23,7 +23,7 @@
/** A linked transition which is driven by a [originalTransition]. */
internal class LinkedTransition(
- private val originalTransition: TransitionState.Transition.ChangeCurrentScene,
+ private val originalTransition: TransitionState.Transition,
fromScene: SceneKey,
toScene: SceneKey,
override val key: TransitionKey? = null,
@@ -32,8 +32,8 @@
override val currentScene: SceneKey
get() {
return when (originalTransition.currentScene) {
- originalTransition.fromScene -> fromScene
- originalTransition.toScene -> toScene
+ originalTransition.fromContent -> fromScene
+ originalTransition.toContent -> toScene
else -> error("Original currentScene is neither FromScene nor ToScene")
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index c29bf21..c830ca4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene.transition.link
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutState
@@ -35,8 +36,8 @@
* target to `SceneA` from any current scene.
*/
class TransitionLink(
- val sourceFrom: SceneKey?,
- val sourceTo: SceneKey?,
+ val sourceFrom: ContentKey?,
+ val sourceTo: ContentKey?,
val targetFrom: SceneKey?,
val targetTo: SceneKey,
val targetTransitionKey: TransitionKey? = null,
@@ -50,15 +51,15 @@
}
internal fun isMatchingLink(
- transition: TransitionState.Transition.ChangeCurrentScene,
+ transition: TransitionState.Transition,
): Boolean {
- return (sourceFrom == null || sourceFrom == transition.fromScene) &&
- (sourceTo == null || sourceTo == transition.toScene)
+ return (sourceFrom == null || sourceFrom == transition.fromContent) &&
+ (sourceTo == null || sourceTo == transition.toContent)
}
- internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
- return (targetFrom == null || targetFrom == targetCurrentScene) &&
- targetTo != targetCurrentScene
+ internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean {
+ return (targetFrom == null || targetFrom == targetCurrentContent) &&
+ targetTo != targetCurrentContent
}
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 25be3f9..7d8e898 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -1020,7 +1020,7 @@
// We scrolled down, under scene C there is nothing, so we can use the overscroll spec
assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull()
- assertThat(layoutState.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneC)
+ assertThat(layoutState.currentTransition?.currentOverscrollSpec?.content).isEqualTo(SceneC)
val transition = layoutState.currentTransition
assertThat(transition).isNotNull()
assertThat(transition!!.progress).isEqualTo(-0.1f)
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/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index d4391e0..85db418 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -16,6 +16,8 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
@@ -33,6 +35,7 @@
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestOverlays.OverlayA
@@ -49,8 +52,8 @@
@get:Rule val rule = createComposeRule()
@Composable
- private fun ContentScope.Foo() {
- Box(Modifier.element(TestElements.Foo).size(100.dp))
+ private fun ContentScope.Foo(width: Dp = 100.dp, height: Dp = 100.dp) {
+ Box(Modifier.element(TestElements.Foo).size(width, height))
}
@Test
@@ -316,4 +319,209 @@
// of the layout.
rule.onNodeWithTag(contentTag).assertSizeIsEqualTo(100.dp)
}
+
+ @Test
+ fun showAnimation() {
+ rule.testShowOverlayTransition(
+ fromSceneContent = {
+ Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+ Foo(width = 60.dp, height = 40.dp)
+ }
+ },
+ overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+ // size of 100x80dp, so at (40,20).
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ after {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+ }
+ }
+
+ @Test
+ fun hideAnimation() {
+ rule.testHideOverlayTransition(
+ toSceneContent = {
+ Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+ Foo(width = 60.dp, height = 40.dp)
+ }
+ },
+ overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from centered (in a 180x120dp Box) with a size of 100x80dp, so at (40,20),
+ // to (0,0) with a size of 60x40dp.
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ after {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ }
+ }
+ }
+
+ @Test
+ fun replaceAnimation() {
+ rule.testReplaceOverlayTransition(
+ currentSceneContent = { Box(Modifier.size(width = 180.dp, height = 120.dp)) },
+ fromContent = { Foo(width = 60.dp, height = 40.dp) },
+ fromAlignment = Alignment.TopStart,
+ toContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+ // size of 100x80dp, so at (40,20).
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ after {
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3422a8e..69f2cba 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -495,7 +495,7 @@
// overscroll for SceneB is defined
progress.value = 1.1f
val overscrollSpec = assertThat(transition).hasOverscrollSpec()
- assertThat(overscrollSpec.scene).isEqualTo(SceneB)
+ assertThat(overscrollSpec.content).isEqualTo(SceneB)
}
@Test
@@ -516,7 +516,7 @@
// overscroll for SceneA is defined
progress.value = -0.1f
val overscrollSpec = assertThat(transition).hasOverscrollSpec()
- assertThat(overscrollSpec.scene).isEqualTo(SceneA)
+ assertThat(overscrollSpec.content).isEqualTo(SceneA)
// scroll from SceneA to SceneB
progress.value = 0.5f
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/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 7f26b98..1ebd3d9 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -16,9 +16,12 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -115,6 +118,97 @@
)
}
+/** Test the transition when showing [overlay] from [fromScene]. */
+fun ComposeContentTestRule.testShowOverlayTransition(
+ fromSceneContent: @Composable ContentScope.() -> Unit,
+ overlayContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ fromScene: SceneKey = TestScenes.SceneA,
+ overlay: OverlayKey = TestOverlays.OverlayA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ fromScene,
+ transitions = transitions { from(fromScene, overlay, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(fromScene) { fromSceneContent() }
+ overlay(overlay) { overlayContent() }
+ }
+ },
+ changeState = { state -> state.showOverlay(overlay, animationScope = this) },
+ builder = builder,
+ )
+}
+
+/** Test the transition when hiding [overlay] to [toScene]. */
+fun ComposeContentTestRule.testHideOverlayTransition(
+ toSceneContent: @Composable ContentScope.() -> Unit,
+ overlayContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ toScene: SceneKey = TestScenes.SceneA,
+ overlay: OverlayKey = TestOverlays.OverlayA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ toScene,
+ initialOverlays = setOf(overlay),
+ transitions = transitions { from(overlay, toScene, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(toScene) { toSceneContent() }
+ overlay(overlay) { overlayContent() }
+ }
+ },
+ changeState = { state -> state.hideOverlay(overlay, animationScope = this) },
+ builder = builder,
+ )
+}
+
+/** Test the transition when replace [from] to [to]. */
+fun ComposeContentTestRule.testReplaceOverlayTransition(
+ fromContent: @Composable ContentScope.() -> Unit,
+ toContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ currentSceneContent: @Composable ContentScope.() -> Unit = { Box(Modifier.fillMaxSize()) },
+ fromAlignment: Alignment = Alignment.Center,
+ toAlignment: Alignment = Alignment.Center,
+ from: OverlayKey = TestOverlays.OverlayA,
+ to: OverlayKey = TestOverlays.OverlayB,
+ currentScene: SceneKey = TestScenes.SceneA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ currentScene,
+ initialOverlays = setOf(from),
+ transitions = transitions { from(from, to, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(currentScene) { currentSceneContent() }
+ overlay(from, fromAlignment) { fromContent() }
+ overlay(to, toAlignment) { toContent() }
+ }
+ },
+ changeState = { state -> state.replaceOverlay(from, to, animationScope = this) },
+ builder = builder,
+ )
+}
+
data class TransitionRecordingSpec(
val recordBefore: Boolean = true,
val recordAfter: Boolean = true,
@@ -188,6 +282,21 @@
"(${currentScene.debugName})"
}
+ testTransition(
+ state = state,
+ changeState = { state -> state.setTargetScene(to, coroutineScope = this) },
+ transitionLayout = transitionLayout,
+ builder = builder,
+ )
+}
+
+/** Test the transition from [state] to [to]. */
+fun ComposeContentTestRule.testTransition(
+ state: MutableSceneTransitionLayoutState,
+ changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
+ transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
val test = transitionTest(builder)
val assertionScope =
object : TransitionTestAssertionScope {
@@ -213,7 +322,7 @@
mainClock.autoAdvance = false
// Change the current scene.
- runOnUiThread { state.setTargetScene(to, coroutineScope) }
+ runOnUiThread { coroutineScope.changeState(state) }
waitForIdle()
mainClock.advanceTimeByFrame()
waitForIdle()
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/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/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/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 91259a6..3e75ceb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -34,6 +34,7 @@
import android.os.CancellationSignal
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags as AConfigFlags
@@ -55,6 +56,7 @@
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -77,6 +79,9 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
@@ -90,6 +95,7 @@
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -136,12 +142,12 @@
@Captor
private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
- private val testDispatcher = kosmos.testDispatcher
+ private val testDispatcher by lazy { kosmos.testDispatcher }
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val testScope = kosmos.testScope
- private val fakeUserRepository = kosmos.fakeUserRepository
- private val fakeExecutor = kosmos.fakeExecutor
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val testScope by lazy { kosmos.testScope }
+ private val fakeUserRepository by lazy { kosmos.fakeUserRepository }
+ private val fakeExecutor by lazy { kosmos.fakeExecutor }
private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
@@ -149,18 +155,19 @@
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
- private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
- private val deviceEntryFingerprintAuthRepository =
+ private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+ private val deviceEntryFingerprintAuthRepository by lazy {
kosmos.fakeDeviceEntryFingerprintAuthRepository
- private val trustRepository = kosmos.fakeTrustRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val powerInteractor = kosmos.powerInteractor
- private val keyguardInteractor = kosmos.keyguardInteractor
- private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
- private val displayStateInteractor = kosmos.displayStateInteractor
- private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
- private val displayRepository = kosmos.displayRepository
- private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ }
+ private val trustRepository by lazy { kosmos.fakeTrustRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
+ private val displayStateInteractor by lazy { kosmos.displayStateInteractor }
+ private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+ private val displayRepository by lazy { kosmos.displayRepository }
+ private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
private lateinit var featureFlags: FakeFeatureFlags
private var wasAuthCancelled = false
@@ -180,9 +187,11 @@
whenever(bypassController.bypassEnabled).thenReturn(true)
underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
+ }
}
private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -227,6 +236,7 @@
powerInteractor,
keyguardInteractor,
alternateBouncerInteractor,
+ { kosmos.sceneInteractor },
faceDetectBuffer,
faceAuthBuffer,
keyguardTransitionInteractor,
@@ -547,6 +557,24 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainerEnabled_authenticateDoesNotRunWhenKeyguardIsGoingAway() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.UNDEFINED,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING
+ ),
+ validateStep = false
+ )
+ runCurrent()
+ }
+ }
+
+ @Test
fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
testScope.runTest {
testGatingCheckForFaceAuth {
@@ -595,6 +623,31 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainer_authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled = true)
+ bouncerRepository.setAlternateVisible(false)
+
+ // launch secure camera
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ keyguardRepository.setKeyguardOccluded(true)
+ kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "for-test")
+ runCurrent()
+ assertThat(canFaceAuthRun()).isFalse()
+
+ // but bouncer is shown after that.
+ kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+ )
+ runCurrent()
+
+ assertThat(canFaceAuthRun()).isTrue()
+ }
+
+ @Test
fun authenticateDoesNotRunOnUnsupportedPosture() =
testScope.runTest {
testGatingCheckForFaceAuth {
@@ -841,6 +894,24 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainer_faceDetectDoesNotRunWhenKeyguardGoingAway() =
+ testScope.runTest {
+ testGatingCheckForDetect(sceneContainerEnabled = true) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.UNDEFINED,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING
+ ),
+ validateStep = false
+ )
+ runCurrent()
+ }
+ }
+
+ @Test
fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
testScope.runTest {
testGatingCheckForDetect {
@@ -1052,10 +1123,11 @@
}
private suspend fun TestScope.testGatingCheckForFaceAuth(
+ sceneContainerEnabled: Boolean = false,
gatingCheckModifier: suspend () -> Unit
) {
initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
gatingCheckModifier()
runCurrent()
@@ -1069,7 +1141,7 @@
faceAuthenticateIsNotCalled()
// flip the gating check back on.
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
assertThat(underTest.canRunFaceAuth.value).isTrue()
faceAuthenticateIsCalled()
@@ -1094,10 +1166,11 @@
}
private suspend fun TestScope.testGatingCheckForDetect(
+ sceneContainerEnabled: Boolean = false,
gatingCheckModifier: suspend () -> Unit
) {
initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
// This will stop face auth from running but is required to be false for detect.
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
@@ -1145,12 +1218,22 @@
cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
}
- private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+ private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
+ sceneContainerEnabled: Boolean = false
+ ) {
fakeExecutor.runAllReady()
verify(faceManager, atLeastOnce())
.addLockoutResetCallback(faceLockoutResetCallback.capture())
trustRepository.setCurrentUserTrusted(false)
- keyguardRepository.setKeyguardGoingAway(false)
+ if (sceneContainerEnabled) {
+ // Keyguard is not going away
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(KeyguardState.OFF, KeyguardState.LOCKSCREEN, value = 1.0f),
+ validateStep = false
+ )
+ } else {
+ keyguardRepository.setKeyguardGoingAway(false)
+ }
powerInteractor.setAwakeForTest()
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
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/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 2b2c121..aee72de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -25,8 +25,11 @@
import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -260,6 +263,23 @@
}
@Test
+ fun triggersFaceAuthWhenLockscreenIsClicked() =
+ testScope.runTest {
+ collectLastValue(underTest.isMenuVisible)
+ runCurrent()
+ kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true
+
+ underTest.onClick(100.0f, 100.0f)
+ runCurrent()
+
+ val runningAuthRequest =
+ kosmos.fakeDeviceEntryFaceAuthRepository.runningAuthRequest.value
+ assertThat(runningAuthRequest?.first)
+ .isEqualTo(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED)
+ assertThat(runningAuthRequest?.second).isEqualTo(true)
+ }
+
+ @Test
fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
testScope.runTest {
val isMenuVisible by collectLastValue(underTest.isMenuVisible)
@@ -302,6 +322,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
accessibilityManager = kosmos.accessibilityManagerWrapper,
pulsingGestureListener = kosmos.pulsingGestureListener,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
)
setUpState()
}
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/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
new file mode 100644
index 0000000..22e5896
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -0,0 +1,399 @@
+/**
+ * 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.media.controls.domain.pipeline
+
+import android.app.Notification
+import android.app.Notification.MediaStyle
+import android.app.PendingIntent
+import android.app.statusBarManager
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.media.AudioAttributes
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.service.notification.StatusBarNotification
+import androidx.media.utils.MediaConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val KEY = "KEY"
+private const val PACKAGE_NAME = "com.example.app"
+private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
+private const val APP_NAME = "SystemUI"
+private const val SESSION_ARTIST = "artist"
+private const val SESSION_TITLE = "title"
+private const val SESSION_EMPTY_TITLE = ""
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaDataLoaderTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
+ private val statusBarManager = kosmos.statusBarManager
+ private val mediaController = mock<MediaController>()
+ private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+ private val mediaFlags = kosmos.mediaFlags
+ private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+ private val session = MediaSession(context, "MediaDataLoaderTestSession")
+ private val metadataBuilder =
+ MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+
+ private val underTest: MediaDataLoader =
+ MediaDataLoader(
+ context,
+ testDispatcher,
+ testScope,
+ kosmos.activityStarter,
+ mediaControllerFactory,
+ mediaFlags,
+ kosmos.imageLoader,
+ statusBarManager
+ )
+
+ @Before
+ fun setUp() {
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+ mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+ }
+
+ @Test
+ fun loadMediaData_returnsMediaData() =
+ testScope.runTest {
+ val song = "THIS_IS_A_SONG"
+ val artist = "THIS_IS_AN_ARTIST"
+ val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+ whenever(mediaController.playbackState)
+ .thenReturn(
+ PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 12, 1.0f).build()
+ )
+ whenever(mediaController.playbackInfo)
+ .thenReturn(
+ MediaController.PlaybackInfo(
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+ 0,
+ 0,
+ 0,
+ AudioAttributes.Builder().build(),
+ null
+ )
+ )
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, song)
+ .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
+ .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt)
+ .putLong(
+ MediaConstants.METADATA_KEY_IS_EXPLICIT,
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ )
+ .build()
+ )
+
+ val result = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result).isNotNull()
+ assertThat(result?.appIcon).isNotNull()
+ assertThat(result?.appIcon?.resId).isEqualTo(android.R.drawable.ic_media_pause)
+ assertThat(result?.artist).isEqualTo(artist)
+ assertThat(result?.song).isEqualTo(song)
+ assertThat(result?.artworkIcon).isNotNull()
+ assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(albumArt.width)
+ assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(albumArt.height)
+ assertThat(result?.token).isEqualTo(session.sessionToken)
+ assertThat(result?.device).isNull()
+ assertThat(result?.playbackLocation).isEqualTo(MediaData.PLAYBACK_LOCAL)
+ assertThat(result?.isPlaying).isTrue()
+ assertThat(result?.isExplicit).isTrue()
+ assertThat(result?.resumeAction).isNull()
+ assertThat(result?.resumeProgress).isNull()
+ }
+
+ @Test
+ fun loadMediaDataForResumption_returnsMediaData() =
+ testScope.runTest {
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
+
+ val song = "THIS_IS_A_SONG"
+ val artist = "THIS_IS_AN_ARTIST"
+ val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+ val extras = Bundle()
+ extras.putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ extras.putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.3)
+ extras.putLong(
+ MediaConstants.METADATA_KEY_IS_EXPLICIT,
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ )
+
+ val description =
+ MediaDescription.Builder()
+ .setTitle(song)
+ .setSubtitle(artist)
+ .setIconBitmap(albumArt)
+ .setExtras(extras)
+ .build()
+
+ val intent =
+ PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+
+ val result =
+ underTest.loadMediaDataForResumption(
+ 0,
+ description,
+ Runnable {},
+ null,
+ session.sessionToken,
+ APP_NAME,
+ intent,
+ PACKAGE_NAME
+ )
+ assertThat(result).isNotNull()
+ assertThat(result?.appName).isEqualTo(APP_NAME)
+ assertThat(result?.song).isEqualTo(song)
+ assertThat(result?.artist).isEqualTo(artist)
+ assertThat(result?.artworkIcon).isNotNull()
+ assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(100)
+ assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(100)
+ assertThat(result?.token).isEqualTo(session.sessionToken)
+ assertThat(result?.clickIntent).isEqualTo(intent)
+ assertThat(result?.isExplicit).isTrue()
+ assertThat(result?.resumeProgress).isEqualTo(0.3)
+ }
+
+ @Test
+ fun loadMediaData_songNameFallbacks() =
+ testScope.runTest {
+ // Check ordering of Song resolution:
+ // DISPLAY_TITLE > TITLE > notification TITLE > notification TITLE_BIG
+
+ // DISPLAY_TITLE
+ whenever(mediaController.metadata)
+ .thenReturn(
+ MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "title1")
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+ .build()
+ )
+ val result1 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result1?.song).isEqualTo("title1")
+
+ // TITLE
+ whenever(mediaController.metadata)
+ .thenReturn(
+ MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+ .build()
+ )
+ val result2 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result2?.song).isEqualTo("title2")
+
+ // notification TITLE
+ val notif =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setContentTitle("notiftitle")
+ }
+ build()
+ }
+ whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+ val result3 = underTest.loadMediaData(KEY, notif)
+ assertThat(result3?.song).isEqualTo("notiftitle")
+
+ // Final fallback
+ whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+ val result4 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result4?.song)
+ .isEqualTo(context.getString(R.string.controls_media_empty_title, result4?.appName))
+ }
+
+ @Test
+ fun loadMediaData_emptyTitle_hasPlaceholder() =
+ testScope.runTest {
+ val packageManager = mock<PackageManager>()
+ context.setMockPackageManager(packageManager)
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+
+ val result = underTest.loadMediaData(KEY, createMediaNotification())
+
+ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+ assertThat(result).isNotNull()
+ assertThat(result?.song).isEqualTo(placeholderTitle)
+ }
+
+ @Test
+ fun loadMediaData_emptyMetadata_usesNotificationTitle() =
+ testScope.runTest {
+ val packageManager = mock<PackageManager>()
+ context.setMockPackageManager(packageManager)
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+ val mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setContentTitle(SESSION_TITLE)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, mediaNotification)
+
+ assertThat(result).isNotNull()
+ assertThat(result?.song).isEqualTo(SESSION_TITLE)
+ }
+
+ @Test
+ fun loadMediaData_badArtwork_isNotUsed() =
+ testScope.runTest {
+ val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setLargeIcon(artwork)
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, mediaNotification)
+
+ assertThat(result).isNotNull()
+ }
+
+ @Test
+ fun loadMediaData_invalidTokenNoCrash() =
+ testScope.runTest {
+ val bundle = Bundle()
+ // wrong data type
+ bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) }
+ )
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, rcn)
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun testLoadMediaDataInBg_invalidMediaRemoteIntentNoCrash() =
+ testScope.runTest {
+ val bundle = Bundle()
+ // wrong data type
+ bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ }
+ )
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, rcn)
+ assertThat(result).isNotNull()
+ }
+
+ private fun createMediaNotification(
+ mediaSession: MediaSession? = session,
+ applicationInfo: ApplicationInfo? = null
+ ): StatusBarNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(mediaSession?.sessionToken) })
+ if (applicationInfo != null) {
+ val bundle = Bundle()
+ bundle.putParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ applicationInfo
+ )
+ it.addExtras(bundle)
+ }
+ }
+ build()
+ }
+}
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/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
index 789a473..f920b18 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
@@ -4,6 +4,7 @@
import android.util.Log
import android.view.View
import androidx.annotation.VisibleForTesting
+import androidx.core.text.util.LocalePreferences
typealias WeatherTouchAction = (View) -> Unit
@@ -54,12 +55,35 @@
}
}
- private fun readIntFromBundle(extras: Bundle, key: String): Int? =
+ private fun readIntFromBundle(extras: Bundle, key: String): Int? {
try {
- extras.getString(key)?.toInt()
+ return extras.getString(key)?.toInt()
} catch (e: Exception) {
- null
+ return null
}
+ }
+
+ fun getPlaceholderWeatherData(): WeatherData {
+ return getPlaceholderWeatherData(
+ LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
+ )
+ }
+
+ private const val DESCRIPTION_PLACEHODLER = ""
+ private const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
+ private const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
+ private val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
+
+ fun getPlaceholderWeatherData(useCelsius: Boolean): WeatherData {
+ return WeatherData(
+ description = DESCRIPTION_PLACEHODLER,
+ state = WEATHERICON_PLACEHOLDER,
+ temperature =
+ if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
+ else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
+ useCelsius = useCelsius,
+ )
+ }
}
// Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
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/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index fc6d20e..0c11d2f 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -27,6 +27,8 @@
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">true</bool>
+ <bool name="config_use_large_screen_shade_header">true</bool>
+
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">2</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index b438315..c594f1c 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,8 +35,6 @@
<!-- How many lines to show in the security footer -->
<integer name="qs_security_footer_maxLines">1</integer>
- <bool name="config_use_large_screen_shade_header">true</bool>
-
<!-- Whether to show bottom sheets edge to edge -->
<bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index fbe1399..e68da09 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -48,6 +48,7 @@
"src/**/*.kt",
"src/**/*.aidl",
":wm_shell-aidls",
+ ":wm_shell-shared-aidls",
":wm_shell_util-sources",
],
static_libs: [
@@ -69,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/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 5dcf161..c1eae2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -477,6 +477,12 @@
smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
+ fun setFallbackWeatherData(data: WeatherData) {
+ if (weatherData != null) return
+ weatherData = data
+ clock?.run { events.onWeatherDataChanged(data) }
+ }
+
/**
* Sets this clock as showing in a secondary display.
*
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/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 2aa6c19..2ae514f 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)
}
/**
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/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 9460eaf..d288cce 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,7 +57,10 @@
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.Scenes.Bouncer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -159,6 +162,7 @@
private val powerInteractor: PowerInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val sceneInteractor: dagger.Lazy<SceneInteractor>,
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -385,7 +389,16 @@
biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
"isFaceAuthEnrolledAndEnabled"
),
- Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+ Pair(
+ if (SceneContainerFlag.isEnabled) {
+ keyguardTransitionInteractor
+ .isInTransitionWhere(toStatePredicate = { it == KeyguardState.UNDEFINED })
+ .isFalse()
+ } else {
+ keyguardRepository.isKeyguardGoingAway.isFalse()
+ },
+ "keyguardNotGoingAway"
+ ),
Pair(
keyguardTransitionInteractor
.isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
@@ -397,7 +410,11 @@
.isFalse()
.or(
alternateBouncerInteractor.isVisible.or(
- keyguardInteractor.primaryBouncerShowing
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.get().transitionState.map { it.isIdle(Bouncer) }
+ } else {
+ keyguardInteractor.primaryBouncerShowing
+ }
)
),
"secureCameraNotActiveOrAnyBouncerIsShowing"
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index c536d6b..183e0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -46,6 +46,9 @@
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
@@ -90,6 +93,7 @@
private val powerInteractor: PowerInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val trustManager: TrustManager,
+ private val sceneInteractor: Lazy<SceneInteractor>,
deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
) : DeviceEntryFaceAuthInteractor {
@@ -103,9 +107,7 @@
keyguardUpdateMonitor.setFaceAuthInteractor(this)
observeFaceAuthStateUpdates()
faceAuthenticationLogger.interactorStarted()
- primaryBouncerInteractor
- .get()
- .isShowing
+ isBouncerVisible
.whenItFlipsToTrue()
.onEach {
faceAuthenticationLogger.bouncerVisibilityChanged()
@@ -181,19 +183,23 @@
// auth so that the switched user can unlock the device with face auth.
userRepository.selectedUser
.pairwise()
- .onEach { (previous, curr) ->
+ .filter { (previous, curr) ->
val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
- if (wasSwitching && !isSwitching) {
- resetLockedOutState(curr.userInfo.id)
- yield()
- runFaceAuth(
- FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
- // Fallback to detection if bouncer is not showing so that we can detect a
- // face and then show the bouncer to the user if face auth can't run
- fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing()
- )
- }
+ // User switching was in progress and is complete now.
+ wasSwitching && !isSwitching
+ }
+ .map { (_, curr) -> curr.userInfo.id }
+ .sample(isBouncerVisible, ::Pair)
+ .onEach { (userId, isBouncerCurrentlyVisible) ->
+ resetLockedOutState(userId)
+ yield()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
+ // Fallback to detection if bouncer is not showing so that we can detect a
+ // face and then show the bouncer to the user if face auth can't run
+ fallbackToDetect = !isBouncerCurrentlyVisible
+ )
}
.launchIn(applicationScope)
@@ -210,6 +216,14 @@
.launchIn(applicationScope)
}
+ private val isBouncerVisible: Flow<Boolean> by lazy {
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
+ } else {
+ primaryBouncerInteractor.get().isShowing
+ }
+ }
+
private suspend fun resetLockedOutState(currentUserId: Int) {
val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
repository.setLockedOut(
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/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index 567bf70..ca43871 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -35,6 +35,7 @@
import android.util.Log
import android.util.Size
import androidx.core.content.res.ResourcesCompat
+import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -162,20 +163,21 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Bitmap? {
- return try {
- ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
- configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
- decoder.allocator = allocator
+ ): Bitmap? =
+ traceSection("ImageLoader#loadBitmap") {
+ return try {
+ ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
}
- } catch (e: IOException) {
- Log.w(TAG, "Failed to load source $source", e)
- return null
- } catch (e: DecodeException) {
- Log.w(TAG, "Failed to decode source $source", e)
- return null
}
- }
/**
* Loads passed [Source] on a background thread and returns the [Drawable].
@@ -253,28 +255,31 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return try {
- loadDrawableSync(
- toImageDecoderSource(source, defaultContext),
- maxWidth,
- maxHeight,
- allocator
- )
- ?:
- // If we have a resource, retry fallback using the "normal" Resource loading system.
- // This will come into effect in cases like trying to load AnimatedVectorDrawable.
- if (source is Res) {
- val context = source.context ?: defaultContext
- ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
- } else {
- null
- }
- } catch (e: NotFoundException) {
- Log.w(TAG, "Couldn't load resource $source", e)
- null
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return try {
+ loadDrawableSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ ?:
+ // If we have a resource, retry fallback using the "normal" Resource loading
+ // system.
+ // This will come into effect in cases like trying to load
+ // AnimatedVectorDrawable.
+ if (source is Res) {
+ val context = source.context ?: defaultContext
+ ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+ } else {
+ null
+ }
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
}
- }
/**
* Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
@@ -297,20 +302,21 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return try {
- ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
- configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
- decoder.allocator = allocator
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return try {
+ ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
}
- } catch (e: IOException) {
- Log.w(TAG, "Failed to load source $source", e)
- return null
- } catch (e: DecodeException) {
- Log.w(TAG, "Failed to decode source $source", e)
- return null
}
- }
/** Loads icon drawable while attempting to size restrict the drawable. */
@WorkerThread
@@ -320,55 +326,59 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return when (icon.type) {
- Icon.TYPE_URI,
- Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
- val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
- loadDrawableSync(source, maxWidth, maxHeight, allocator)
- }
- Icon.TYPE_RESOURCE -> {
- val resources = resolveResourcesForIcon(context, icon)
- resources?.let {
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+ Icon.TYPE_RESOURCE -> {
+ val resources = resolveResourcesForIcon(context, icon)
+ resources?.let {
+ loadDrawableSync(
+ ImageDecoder.createSource(it, icon.resId),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ // Fallback to non-ImageDecoder load if the attempt failed (e.g. the
+ // resource
+ // is a Vector drawable which ImageDecoder doesn't support.)
+ ?: loadIconDrawable(icon, context)
+ }
+ Icon.TYPE_BITMAP -> {
+ BitmapDrawable(context.resources, icon.bitmap)
+ }
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+ }
+ Icon.TYPE_DATA -> {
loadDrawableSync(
- ImageDecoder.createSource(it, icon.resId),
+ ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
maxWidth,
maxHeight,
allocator
)
}
- // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
- // is a Vector drawable which ImageDecoder doesn't support.)
- ?: loadIconDrawable(icon, context)
+ else -> {
+ // We don't recognize this icon, just fallback.
+ loadIconDrawable(icon, context)
+ }
+ }?.let { drawable ->
+ // Icons carry tint which we need to propagate down to a Drawable.
+ tintDrawable(icon, drawable)
+ drawable
}
- Icon.TYPE_BITMAP -> {
- BitmapDrawable(context.resources, icon.bitmap)
- }
- Icon.TYPE_ADAPTIVE_BITMAP -> {
- AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
- }
- Icon.TYPE_DATA -> {
- loadDrawableSync(
- ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
- maxWidth,
- maxHeight,
- allocator
- )
- }
- else -> {
- // We don't recognize this icon, just fallback.
- loadIconDrawable(icon, context)
- }
- }?.let { drawable ->
- // Icons carry tint which we need to propagate down to a Drawable.
- tintDrawable(icon, drawable)
- drawable
}
- }
@WorkerThread
fun loadIconDrawable(icon: Icon, context: Context): Drawable? {
- icon.loadDrawable(context)?.let { return it }
+ icon.loadDrawable(context)?.let {
+ return it
+ }
Log.w(TAG, "Failed to load drawable for $icon")
return null
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/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index cd49c6a..4a8ada7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -27,6 +27,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -67,6 +68,7 @@
broadcastDispatcher: BroadcastDispatcher,
private val accessibilityManager: AccessibilityManagerWrapper,
private val pulsingGestureListener: PulsingGestureListener,
+ private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
) {
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: StateFlow<Boolean> =
@@ -129,7 +131,8 @@
}
}
- /** Notifies that the user has long-pressed on the lock screen.
+ /**
+ * Notifies that the user has long-pressed on the lock screen.
*
* @param isA11yAction: Whether the action was performed as an a11y action
*/
@@ -174,6 +177,7 @@
/** Notifies that the lockscreen has been clicked at position [x], [y]. */
fun onClick(x: Float, y: Float) {
pulsingGestureListener.onSingleTapUp(x, y)
+ faceAuthInteractor.onNotificationPanelClicked()
}
/** Notifies that the lockscreen has been double clicked. */
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 51ce355..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
@@ -79,6 +79,7 @@
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.clocks.ClockRegistry
@@ -188,6 +189,7 @@
init {
coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
disposables += DisposableHandle { coroutineScope.cancel() }
+ clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
if (KeyguardBottomAreaRefactor.isEnabled) {
quickAffordancesCombinedViewModel.enablePreviewMode(
@@ -416,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/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/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
similarity index 70%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
index c968e80..b0abdb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
@@ -14,6 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.systemui.log.dagger
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+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/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 143d66b..24c57be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -16,9 +16,8 @@
package com.android.systemui.media.controls.domain.pipeline
+import android.annotation.MainThread
import android.annotation.SuppressLint
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
@@ -39,7 +38,6 @@
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
-import android.graphics.drawable.Animatable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -62,8 +60,10 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Dumpable
+import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -86,7 +86,6 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.tuner.TunerService
@@ -97,8 +96,13 @@
import com.android.systemui.util.time.SystemClock
import java.io.IOException
import java.io.PrintWriter
+import java.util.Collections
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
// URI fields to try loading album art from
private val ART_URIS =
@@ -167,8 +171,11 @@
class LegacyMediaDataManagerImpl(
private val context: Context,
@Background private val backgroundExecutor: Executor,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
@Main private val uiExecutor: Executor,
@Main private val foregroundExecutor: DelayableExecutor,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Application private val applicationScope: CoroutineScope,
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
dumpManager: DumpManager,
@@ -188,6 +195,7 @@
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
companion object {
@@ -219,7 +227,12 @@
// listeners are listeners that depend on MediaDataManager.
// TODO(b/159539991#comment5): Move internal listeners to separate package.
private val internalListeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
- private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+ private val mediaEntries: MutableMap<String, MediaData> =
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ Collections.synchronizedMap(LinkedHashMap())
+ } else {
+ LinkedHashMap()
+ }
// There should ONLY be at most one Smartspace media recommendation.
var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
@Keep private var smartspaceSession: SmartspaceSession? = null
@@ -245,8 +258,11 @@
constructor(
context: Context,
threadFactory: ThreadFactory,
+ @Background backgroundDispatcher: CoroutineDispatcher,
@Main uiExecutor: Executor,
@Main foregroundExecutor: DelayableExecutor,
+ @Main mainDispatcher: CoroutineDispatcher,
+ @Application applicationScope: CoroutineScope,
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
broadcastDispatcher: BroadcastDispatcher,
@@ -264,13 +280,17 @@
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : this(
context,
// Loading bitmap for UMO background can take longer time, so it cannot run on the default
// background thread. Use a custom thread for media.
threadFactory.buildExecutorOnNewThread(TAG),
+ backgroundDispatcher,
uiExecutor,
foregroundExecutor,
+ mainDispatcher,
+ applicationScope,
mediaControllerFactory,
broadcastDispatcher,
dumpManager,
@@ -290,6 +310,7 @@
logger,
smartspaceManager,
keyguardUpdateMonitor,
+ mediaDataLoader,
)
private val appChangeReceiver =
@@ -464,16 +485,31 @@
logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
- backgroundExecutor.execute {
- loadMediaDataInBgForResumption(
- userId,
- desc,
- action,
- token,
- appName,
- appIntent,
- packageName
- )
+
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
+ } else {
+ backgroundExecutor.execute {
+ loadMediaDataInBgForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
}
}
@@ -498,9 +534,90 @@
oldKey: String?,
isNewlyActiveEntry: Boolean = false,
) {
- backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry)
+ }
+ } else {
+ backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ }
}
+ private suspend fun loadMediaDataWithLoader(
+ key: String,
+ sbn: StatusBarNotification,
+ oldKey: String?,
+ isNewlyActiveEntry: Boolean = false,
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val result = mediaDataLoader.get().loadMediaData(key, sbn)
+ if (result == null) {
+ Log.d(TAG, "No result from loadMediaData")
+ return@withContext
+ }
+
+ val currentEntry = mediaEntries[key]
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val resumeAction: Runnable? = currentEntry?.resumeAction
+ val hasCheckedForResume = currentEntry?.hasCheckedForResume == true
+ val active = currentEntry?.active ?: true
+
+ // We need to log the correct media added.
+ if (isNewlyActiveEntry) {
+ logSingleVsMultipleMediaAdded(result.appUid, sbn.packageName, instanceId)
+ logger.logActiveMediaAdded(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ } else if (result.playbackLocation != currentEntry?.playbackLocation) {
+ logger.logPlaybackLocationChange(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ }
+
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ key,
+ oldKey,
+ MediaData(
+ userId = sbn.normalizedUserId,
+ initialized = true,
+ app = result.appName,
+ appIcon = result.appIcon,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = sbn.packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = active,
+ resumeAction = resumeAction,
+ playbackLocation = result.playbackLocation,
+ notificationKey = key,
+ hasCheckedForResume = hasCheckedForResume,
+ isPlaying = result.isPlaying,
+ isClearable = !sbn.isOngoing,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ )
+ )
+ }
+ }
+
/** Add a listener for changes in this class */
override fun addListener(listener: MediaDataManager.Listener) {
// mediaDataFilter is the current end of the internal pipeline. Register external
@@ -697,6 +814,75 @@
notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
}
+ private suspend fun loadMediaDataForResumption(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val currentEntry = mediaEntries[packageName]
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val result =
+ mediaDataLoader
+ .get()
+ .loadMediaDataForResumption(
+ userId,
+ desc,
+ resumeAction,
+ currentEntry,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ if (result == null || desc.title.isNullOrBlank()) {
+ Log.d(TAG, "No MediaData result for resumption")
+ mediaEntries.remove(packageName)
+ return@withContext
+ }
+
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ packageName,
+ null,
+ MediaData(
+ userId = userId,
+ initialized = true,
+ app = result.appName,
+ appIcon = null,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = false,
+ resumeAction = resumeAction,
+ resumption = true,
+ notificationKey = packageName,
+ hasCheckedForResume = true,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ resumeProgress = result.resumeProgress,
+ )
+ )
+ }
+ }
+
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadMediaDataInBgForResumption(
userId: Int,
desc: MediaDescription,
@@ -780,6 +966,7 @@
}
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
fun loadMediaDataInBg(
key: String,
sbn: StatusBarNotification,
@@ -802,8 +989,7 @@
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
ApplicationInfo::class.java
- )
- ?: getAppInfoFromPackage(sbn.packageName)
+ ) ?: getAppInfoFromPackage(sbn.packageName)
// App name
val appName = getAppName(sbn, appInfo)
@@ -894,7 +1080,7 @@
var actionsToShowCollapsed: List<Int> = emptyList()
val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
if (semanticActions == null) {
- val actions = createActionsFromNotification(sbn)
+ val actions = createActionsFromNotification(context, activityStarter, sbn)
actionIcons = actions.first
actionsToShowCollapsed = actions.second
}
@@ -975,6 +1161,7 @@
}
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
try {
return context.packageManager.getApplicationInfo(packageName, 0)
@@ -984,6 +1171,7 @@
return null
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
if (name != null) {
@@ -997,264 +1185,19 @@
}
}
- /** Generate action buttons based on notification actions */
- private fun createActionsFromNotification(
- sbn: StatusBarNotification
- ): Pair<List<MediaAction>, List<Int>> {
- val notif = sbn.notification
- val actionIcons: MutableList<MediaAction> = ArrayList()
- val actions = notif.actions
- var actionsToShowCollapsed =
- notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
- ?: mutableListOf()
- if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
- Log.e(
- TAG,
- "Too many compact actions for ${sbn.key}," +
- "limiting to first $MAX_COMPACT_ACTIONS"
- )
- actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
- }
-
- if (actions != null) {
- for ((index, action) in actions.withIndex()) {
- if (index == MAX_NOTIFICATION_ACTIONS) {
- Log.w(
- TAG,
- "Too many notification actions for ${sbn.key}," +
- " limiting to first $MAX_NOTIFICATION_ACTIONS"
- )
- break
- }
- if (action.getIcon() == null) {
- if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
- actionsToShowCollapsed.remove(index)
- continue
- }
- val runnable =
- if (action.actionIntent != null) {
- Runnable {
- if (action.actionIntent.isActivity) {
- activityStarter.startPendingIntentDismissingKeyguard(
- action.actionIntent
- )
- } else if (action.isAuthenticationRequired()) {
- activityStarter.dismissKeyguardThenExecute(
- {
- var result = sendPendingIntent(action.actionIntent)
- result
- },
- {},
- true
- )
- } else {
- sendPendingIntent(action.actionIntent)
- }
- }
- } else {
- null
- }
- val mediaActionIcon =
- if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
- Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
- } else {
- action.getIcon()
- }
- .setTint(themeText)
- .loadDrawable(context)
- val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
- actionIcons.add(mediaAction)
- }
- }
- return Pair(actionIcons, actionsToShowCollapsed)
- }
-
- /**
- * Generates action button info for this media session based on the PlaybackState
- *
- * @param packageName Package name for the media app
- * @param controller MediaController for the current session
- * @return a Pair consisting of a list of media actions, and a list of ints representing which
- *
- * ```
- * of those actions should be shown in the compact player
- * ```
- */
private fun createActionsFromState(
packageName: String,
controller: MediaController,
user: UserHandle
): MediaButton? {
- val state = controller.playbackState
- if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+ if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
}
-
- // First, check for standard actions
- 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)
- (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),
- // Specify a rebind id to prevent the spinner from restarting on later binds.
- com.android.internal.R.drawable.progress_small_material
- )
- } else if (isPlayingState(state.state)) {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
- } else {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
- }
- val prevButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
- val nextButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
-
- // Then, create a way to build any custom actions that will be needed
- val customActions =
- state.customActions
- .asSequence()
- .filterNotNull()
- .map { getCustomAction(state, packageName, controller, it) }
- .iterator()
- fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
-
- // Finally, assign the remaining button slots: play/pause A B C D
- // A = previous, else custom action (if not reserved)
- // B = next, else custom action (if not reserved)
- // C and D are always custom actions
- val reservePrev =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
- ) == true
- val reserveNext =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
- ) == true
-
- val prevOrCustom =
- if (prevButton != null) {
- prevButton
- } else if (!reservePrev) {
- nextCustomAction()
- } else {
- null
- }
-
- val nextOrCustom =
- if (nextButton != null) {
- nextButton
- } else if (!reserveNext) {
- nextCustomAction()
- } else {
- null
- }
-
- return MediaButton(
- playOrPause,
- nextOrCustom,
- prevOrCustom,
- nextCustomAction(),
- nextCustomAction(),
- reserveNext,
- reservePrev
- )
- }
-
- /**
- * Create a [MediaAction] for a given action and media session
- *
- * @param controller MediaController for the session
- * @param stateActions The actions included with the session's [PlaybackState]
- * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
- * ```
- * [PlaybackState.ACTION_PLAY]
- * [PlaybackState.ACTION_PAUSE]
- * [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
- * [PlaybackState.ACTION_SKIP_TO_NEXT]
- * @return
- * ```
- *
- * A [MediaAction] with correct values set, or null if the state doesn't support it
- */
- private fun getStandardAction(
- controller: MediaController,
- stateActions: Long,
- @PlaybackState.Actions action: Long
- ): MediaAction? {
- if (!includesAction(stateActions, action)) {
- return null
- }
-
- return when (action) {
- PlaybackState.ACTION_PLAY -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
- { controller.transportControls.play() },
- context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
- )
- }
- PlaybackState.ACTION_PAUSE -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
- { controller.transportControls.pause() },
- context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
- )
- }
- PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_prev),
- { controller.transportControls.skipToPrevious() },
- context.getString(R.string.controls_media_button_prev),
- null
- )
- }
- PlaybackState.ACTION_SKIP_TO_NEXT -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_next),
- { controller.transportControls.skipToNext() },
- context.getString(R.string.controls_media_button_next),
- null
- )
- }
- else -> null
- }
- }
-
- /** Check whether the actions from a [PlaybackState] include a specific action */
- private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
- if (
- (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
- (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
- ) {
- return true
- }
- return (stateActions and action != 0L)
- }
-
- /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
- private fun getCustomAction(
- state: PlaybackState,
- packageName: String,
- controller: MediaController,
- customAction: PlaybackState.CustomAction
- ): MediaAction {
- return MediaAction(
- Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
- { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
- customAction.name,
- null
- )
+ return createActionsFromState(context, packageName, controller)
}
/** Load a bitmap from the various Art metadata URIs */
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
for (uri in ART_URIS) {
val uriString = metadata.getString(uri)
@@ -1269,21 +1212,6 @@
return null
}
- private fun sendPendingIntent(intent: PendingIntent): Boolean {
- return try {
- val options = BroadcastOptions.makeBasic()
- options.setInteractive(true)
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
- intent.send(options.toBundle())
- true
- } catch (e: PendingIntent.CanceledException) {
- Log.d(TAG, "Intent canceled", e)
- false
- }
- }
-
/** Returns a bitmap if the user can access the given URI, else null */
private fun loadBitmapFromUriForUser(
uri: Uri,
@@ -1364,6 +1292,7 @@
)
}
+ @MainThread
fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
traceSection("MediaDataManager#onMediaDataLoaded") {
Assert.isMainThread()
@@ -1619,6 +1548,7 @@
* - If resumption is disabled, we only want to show active players
*/
override fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
override fun isRecommendationActive() = smartspaceMediaData.isActive
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
new file mode 100644
index 0000000..70189b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -0,0 +1,311 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Icon
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import androidx.media.utils.MediaConstants
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.util.kotlin.logI
+
+private const val TAG = "MediaActions"
+
+/**
+ * Generates action button info for this media session based on the PlaybackState
+ *
+ * @param packageName Package name for the media app
+ * @param controller MediaController for the current session
+ * @return a Pair consisting of a list of media actions, and a list of ints representing which of
+ * those actions should be shown in the compact player
+ */
+fun createActionsFromState(
+ context: Context,
+ packageName: String,
+ controller: MediaController,
+): MediaButton? {
+ val state = controller.playbackState ?: return null
+ // First, check for standard actions
+ 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)
+ (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),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material
+ )
+ } else if (isPlayingState(state.state)) {
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
+ } else {
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PLAY)
+ }
+ val prevButton =
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+ val nextButton =
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
+
+ // Then, create a way to build any custom actions that will be needed
+ val customActions =
+ state.customActions
+ .asSequence()
+ .filterNotNull()
+ .map { getCustomAction(context, packageName, controller, it) }
+ .iterator()
+ fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+ // Finally, assign the remaining button slots: play/pause A B C D
+ // A = previous, else custom action (if not reserved)
+ // B = next, else custom action (if not reserved)
+ // C and D are always custom actions
+ val reservePrev =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
+ ) == true
+ val reserveNext =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
+ ) == true
+
+ val prevOrCustom =
+ if (prevButton != null) {
+ prevButton
+ } else if (!reservePrev) {
+ nextCustomAction()
+ } else {
+ null
+ }
+
+ val nextOrCustom =
+ if (nextButton != null) {
+ nextButton
+ } else if (!reserveNext) {
+ nextCustomAction()
+ } else {
+ null
+ }
+
+ return MediaButton(
+ playOrPause,
+ nextOrCustom,
+ prevOrCustom,
+ nextCustomAction(),
+ nextCustomAction(),
+ reserveNext,
+ reservePrev
+ )
+}
+
+/**
+ * Create a [MediaAction] for a given action and media session
+ *
+ * @param controller MediaController for the session
+ * @param stateActions The actions included with the session's [PlaybackState]
+ * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
+ * [PlaybackState.ACTION_PLAY] [PlaybackState.ACTION_PAUSE]
+ * [PlaybackState.ACTION_SKIP_TO_PREVIOUS] [PlaybackState.ACTION_SKIP_TO_NEXT]
+ * @return A [MediaAction] with correct values set, or null if the state doesn't support it
+ */
+private fun getStandardAction(
+ context: Context,
+ controller: MediaController,
+ stateActions: Long,
+ @PlaybackState.Actions action: Long
+): MediaAction? {
+ if (!includesAction(stateActions, action)) {
+ return null
+ }
+
+ return when (action) {
+ PlaybackState.ACTION_PLAY -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_play),
+ { controller.transportControls.play() },
+ context.getString(R.string.controls_media_button_play),
+ context.getDrawable(R.drawable.ic_media_play_container)
+ )
+ }
+ PlaybackState.ACTION_PAUSE -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_pause),
+ { controller.transportControls.pause() },
+ context.getString(R.string.controls_media_button_pause),
+ context.getDrawable(R.drawable.ic_media_pause_container)
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_prev),
+ { controller.transportControls.skipToPrevious() },
+ context.getString(R.string.controls_media_button_prev),
+ null
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_NEXT -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_next),
+ { controller.transportControls.skipToNext() },
+ context.getString(R.string.controls_media_button_next),
+ null
+ )
+ }
+ else -> null
+ }
+}
+
+/** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
+private fun getCustomAction(
+ context: Context,
+ packageName: String,
+ controller: MediaController,
+ customAction: PlaybackState.CustomAction
+): MediaAction {
+ return MediaAction(
+ Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
+ { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
+ customAction.name,
+ null
+ )
+}
+
+/** Check whether the actions from a [PlaybackState] include a specific action */
+private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
+ if (
+ (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
+ (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
+ ) {
+ return true
+ }
+ return (stateActions and action != 0L)
+}
+
+/** Generate action buttons based on notification actions */
+fun createActionsFromNotification(
+ context: Context,
+ activityStarter: ActivityStarter,
+ sbn: StatusBarNotification
+): Pair<List<MediaAction>, List<Int>> {
+ val notif = sbn.notification
+ val actionIcons: MutableList<MediaAction> = ArrayList()
+ val actions = notif.actions
+ var actionsToShowCollapsed =
+ notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
+ ?: mutableListOf()
+ if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
+ Log.e(
+ TAG,
+ "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS"
+ )
+ actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
+ }
+
+ actions?.let {
+ if (it.size > MAX_NOTIFICATION_ACTIONS) {
+ Log.w(
+ TAG,
+ "Too many notification actions for ${sbn.key}, " +
+ "limiting to first $MAX_NOTIFICATION_ACTIONS"
+ )
+ }
+
+ for ((index, action) in it.take(MAX_NOTIFICATION_ACTIONS).withIndex()) {
+ if (action.getIcon() == null) {
+ logI(TAG) { "No icon for action $index ${action.title}" }
+ actionsToShowCollapsed.remove(index)
+ continue
+ }
+
+ val runnable =
+ action.actionIntent?.let { actionIntent ->
+ Runnable {
+ when {
+ actionIntent.isActivity ->
+ activityStarter.startPendingIntentDismissingKeyguard(
+ action.actionIntent
+ )
+ action.isAuthenticationRequired ->
+ activityStarter.dismissKeyguardThenExecute(
+ { sendPendingIntent(action.actionIntent) },
+ {},
+ true
+ )
+ else -> sendPendingIntent(actionIntent)
+ }
+ }
+ }
+
+ val themeText =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ .defaultColor
+
+ val mediaActionIcon =
+ when (action.getIcon().type) {
+ Icon.TYPE_RESOURCE ->
+ Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
+ else -> action.getIcon()
+ }
+ .setTint(themeText)
+ .loadDrawable(context)
+
+ val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
+ actionIcons.add(mediaAction)
+ }
+ }
+ return Pair(actionIcons, actionsToShowCollapsed)
+}
+
+private fun sendPendingIntent(intent: PendingIntent): Boolean {
+ return try {
+ intent.send(
+ BroadcastOptions.makeBasic()
+ .apply {
+ setInteractive(true)
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ }
+ .toBundle()
+ )
+ true
+ } catch (e: PendingIntent.CanceledException) {
+ Log.d(TAG, "Intent canceled", e)
+ false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
new file mode 100644
index 0000000..f9fef8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -0,0 +1,530 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.annotation.WorkerThread
+import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
+import android.app.PendingIntent
+import android.app.StatusBarManager
+import android.app.UriGrantsManager
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.drawable.Icon
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.net.Uri
+import android.os.Process
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.support.v4.media.MediaMetadataCompat
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.graphics.ImageLoader
+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.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.kotlin.logD
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.ensureActive
+
+/** Loads media information from media style [StatusBarNotification] classes. */
+@SysUISingleton
+class MediaDataLoader
+@Inject
+constructor(
+ @Application val context: Context,
+ @Main val mainDispatcher: CoroutineDispatcher,
+ @Background val backgroundScope: CoroutineScope,
+ private val activityStarter: ActivityStarter,
+ private val mediaControllerFactory: MediaControllerFactory,
+ private val mediaFlags: MediaFlags,
+ private val imageLoader: ImageLoader,
+ private val statusBarManager: StatusBarManager,
+) {
+ private val mediaProcessingJobs = ConcurrentHashMap<JobKey, Job>()
+
+ private val artworkWidth: Int =
+ context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
+ )
+ private val artworkHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+
+ private val themeText =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ .defaultColor
+
+ /**
+ * Loads media data for a given [StatusBarNotification]. It does the loading on the background
+ * thread.
+ *
+ * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed. The method
+ * suspends until loading has completed or failed.
+ *
+ * If a new [loadMediaData] is issued while existing load is in progress, the existing (old)
+ * load will be cancelled.
+ */
+ suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? {
+ logD(TAG) { "Loading media data for $key..." }
+ val jobKey = JobKey(key, sbn)
+ val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) }
+ loadMediaJob.invokeOnCompletion { mediaProcessingJobs.remove(jobKey) }
+ val existingJob = mediaProcessingJobs.put(jobKey, loadMediaJob)
+ existingJob?.cancel("New processing job incoming.")
+ return loadMediaJob.await()
+ }
+
+ /** Loads media data, should be called from [backgroundScope]. */
+ @WorkerThread
+ private suspend fun loadMediaDataInBackground(
+ key: String,
+ sbn: StatusBarNotification,
+ ): MediaDataLoaderResult? =
+ traceCoroutine("MediaDataLoader#loadMediaData") {
+ val token =
+ sbn.notification.extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token::class.java
+ )
+ if (token == null) {
+ Log.i(TAG, "Token was null, not loading media info")
+ return null
+ }
+ val mediaController = mediaControllerFactory.create(token)
+ val metadata = mediaController.metadata
+ val notification: Notification = sbn.notification
+
+ val appInfo =
+ notification.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ ) ?: getAppInfoFromPackage(sbn.packageName)
+
+ // App name
+ val appName = getAppName(sbn, appInfo)
+
+ // Song name
+ var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+ if (song.isNullOrBlank()) {
+ song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+ }
+ if (song.isNullOrBlank()) {
+ song = HybridGroupManager.resolveTitle(notification)
+ }
+ if (song.isNullOrBlank()) {
+ // For apps that don't include a title, log and add a placeholder
+ song = context.getString(R.string.controls_media_empty_title, appName)
+ try {
+ statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier)
+ } catch (e: RuntimeException) {
+ Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}")
+ }
+ }
+
+ // Don't attempt to load bitmaps if the job was cancelled.
+ coroutineContext.ensureActive()
+
+ // Album art
+ var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
+ }
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+ }
+ val artworkIcon =
+ if (artworkBitmap == null) {
+ notification.getLargeIcon()
+ } else {
+ Icon.createWithBitmap(artworkBitmap)
+ }
+
+ // Don't continue if we were cancelled during slow bitmap load.
+ coroutineContext.ensureActive()
+
+ // App Icon
+ val smallIcon = sbn.notification.smallIcon
+
+ // Explicit Indicator
+ val isExplicit =
+ MediaMetadataCompat.fromMediaMetadata(metadata)
+ ?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+ // Artist name
+ var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
+ if (artist.isNullOrBlank()) {
+ artist = HybridGroupManager.resolveText(notification)
+ }
+
+ // Device name (used for remote cast notifications)
+ val device: MediaDeviceData? = getDeviceInfoForRemoteCast(key, sbn)
+
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState, create actions from session
+ // info
+ // Otherwise, use the notification actions
+ var actionIcons: List<MediaAction> = emptyList()
+ var actionsToShowCollapsed: List<Int> = emptyList()
+ val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
+ logD(TAG) { "Semantic actions: $semanticActions" }
+ if (semanticActions == null) {
+ val actions = createActionsFromNotification(context, activityStarter, sbn)
+ actionIcons = actions.first
+ actionsToShowCollapsed = actions.second
+ logD(TAG) { "[!!] Semantic actions: $semanticActions" }
+ }
+
+ val playbackLocation = getPlaybackLocation(sbn, mediaController)
+ val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) }
+
+ val appUid = appInfo?.uid ?: Process.INVALID_UID
+ return MediaDataLoaderResult(
+ appName = appName,
+ appIcon = smallIcon,
+ artist = artist,
+ song = song,
+ artworkIcon = artworkIcon,
+ actionIcons = actionIcons,
+ actionsToShowInCompact = actionsToShowCollapsed,
+ semanticActions = semanticActions,
+ token = token,
+ clickIntent = notification.contentIntent,
+ device = device,
+ playbackLocation = playbackLocation,
+ isPlaying = isPlaying,
+ appUid = appUid,
+ isExplicit = isExplicit
+ )
+ }
+
+ /**
+ * Loads media data in background for a given set of resumption parameters. The method suspends
+ * until loading is complete or fails.
+ *
+ * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed.
+ */
+ suspend fun loadMediaDataForResumption(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ currentEntry: MediaData?,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ): MediaDataLoaderResult? {
+ val mediaData =
+ backgroundScope.async {
+ loadMediaDataForResumptionInBackground(
+ userId,
+ desc,
+ resumeAction,
+ currentEntry,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
+ return mediaData.await()
+ }
+
+ /** Loads media data for resumption, should be called from [backgroundScope]. */
+ @WorkerThread
+ private suspend fun loadMediaDataForResumptionInBackground(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ currentEntry: MediaData?,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ): MediaDataLoaderResult? =
+ traceCoroutine("MediaDataLoader#loadMediaDataForResumption") {
+ if (desc.title.isNullOrBlank()) {
+ Log.e(TAG, "Description incomplete")
+ return null
+ }
+
+ logD(TAG) { "adding track for $userId from browser: $desc" }
+
+ val appUid = currentEntry?.appUid ?: Process.INVALID_UID
+
+ // Album art
+ var artworkBitmap = desc.iconBitmap
+ if (artworkBitmap == null && desc.iconUri != null) {
+ artworkBitmap =
+ loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName)
+ }
+ val artworkIcon =
+ if (artworkBitmap != null) {
+ Icon.createWithBitmap(artworkBitmap)
+ } else {
+ null
+ }
+
+ val isExplicit =
+ desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+ val progress =
+ if (mediaFlags.isResumeProgressEnabled()) {
+ MediaDataUtils.getDescriptionProgress(desc.extras)
+ } else null
+
+ val mediaAction = getResumeMediaAction(resumeAction)
+ return MediaDataLoaderResult(
+ appName = appName,
+ appIcon = null,
+ artist = desc.subtitle,
+ song = desc.title,
+ artworkIcon = artworkIcon,
+ actionIcons = listOf(mediaAction),
+ actionsToShowInCompact = listOf(0),
+ semanticActions = MediaButton(playOrPause = mediaAction),
+ token = token,
+ clickIntent = appIntent,
+ device = null,
+ playbackLocation = 0,
+ isPlaying = null,
+ appUid = appUid,
+ isExplicit = isExplicit,
+ resumeAction = resumeAction,
+ resumeProgress = progress
+ )
+ }
+
+ private fun createActionsFromState(
+ packageName: String,
+ controller: MediaController,
+ user: UserHandle
+ ): MediaButton? {
+ if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+ return null
+ }
+
+ return createActionsFromState(context, packageName, controller)
+ }
+
+ private fun getPlaybackLocation(sbn: StatusBarNotification, mediaController: MediaController) =
+ when {
+ isRemoteCastNotification(sbn) -> MediaData.PLAYBACK_CAST_REMOTE
+ mediaController.playbackInfo?.playbackType ==
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> MediaData.PLAYBACK_LOCAL
+ else -> MediaData.PLAYBACK_CAST_LOCAL
+ }
+
+ /**
+ * Returns [MediaDeviceData] if the [StatusBarNotification] is a remote cast notification.
+ * `null` otherwise.
+ */
+ private fun getDeviceInfoForRemoteCast(
+ key: String,
+ sbn: StatusBarNotification
+ ): MediaDeviceData? {
+ val extras = sbn.notification.extras
+ val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
+ val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
+ val deviceIntent =
+ extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java)
+ logD(TAG) { "$key is RCN for $deviceName" }
+
+ if (deviceName != null && deviceIcon > -1) {
+ // Name and icon must be present, but intent may be null
+ val enabled = deviceIntent != null && deviceIntent.isActivity
+ val deviceDrawable =
+ Icon.createWithResource(sbn.packageName, deviceIcon)
+ .loadDrawable(sbn.getPackageContext(context))
+ return MediaDeviceData(
+ enabled,
+ deviceDrawable,
+ deviceName,
+ deviceIntent,
+ showBroadcastButton = false
+ )
+ }
+ return null
+ }
+
+ private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+ try {
+ return context.packageManager.getApplicationInfo(packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app info for $packageName", e)
+ return null
+ }
+ }
+
+ private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+ val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+ return when {
+ name != null -> name
+ appInfo != null -> context.packageManager.getApplicationLabel(appInfo).toString()
+ else -> sbn.packageName
+ }
+ }
+
+ /** Load a bitmap from the various Art metadata URIs */
+ private suspend fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
+ for (uri in ART_URIS) {
+ val uriString = metadata.getString(uri)
+ if (!TextUtils.isEmpty(uriString)) {
+ val albumArt = loadBitmapFromUri(Uri.parse(uriString))
+ // If we got cancelled during slow album art load, cancel the rest of
+ // the process.
+ coroutineContext.ensureActive()
+ if (albumArt != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "loaded art from $uri")
+ }
+ return albumArt
+ }
+ }
+ }
+ return null
+ }
+
+ private suspend fun loadBitmapFromUri(uri: Uri): Bitmap? {
+ // ImageDecoder requires a scheme of the following types
+ if (
+ uri.scheme !in
+ listOf(
+ ContentResolver.SCHEME_CONTENT,
+ ContentResolver.SCHEME_ANDROID_RESOURCE,
+ ContentResolver.SCHEME_FILE
+ )
+ ) {
+ Log.w(TAG, "Invalid album art uri $uri")
+ return null
+ }
+
+ val source = ImageLoader.Uri(uri)
+ return imageLoader.loadBitmap(
+ source,
+ artworkWidth,
+ artworkHeight,
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ )
+ }
+
+ private suspend fun loadBitmapFromUriForUser(
+ uri: Uri,
+ userId: Int,
+ appUid: Int,
+ packageName: String
+ ): Bitmap? {
+ try {
+ val ugm = UriGrantsManager.getService()
+ ugm.checkGrantUriPermission_ignoreNonSystem(
+ appUid,
+ packageName,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, userId)
+ )
+ return loadBitmapFromUri(uri)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Failed to get URI permission: $e")
+ }
+ return null
+ }
+
+ /** Check whether this notification is an RCN */
+ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean =
+ sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
+
+ private fun getResumeMediaAction(action: Runnable): MediaAction {
+ return MediaAction(
+ Icon.createWithResource(context, R.drawable.ic_media_play)
+ .setTint(themeText)
+ .loadDrawable(context),
+ action,
+ context.getString(R.string.controls_media_resume),
+ context.getDrawable(R.drawable.ic_media_play_container)
+ )
+ }
+
+ private data class JobKey(val key: String, val sbn: StatusBarNotification) :
+ Pair<String, StatusBarNotification>(key, sbn)
+
+ companion object {
+ private const val TAG = "MediaDataLoader"
+ private val ART_URIS =
+ arrayOf(
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ )
+ }
+
+ /** Returned data from loader. */
+ data class MediaDataLoaderResult(
+ val appName: String?,
+ val appIcon: Icon?,
+ val artist: CharSequence?,
+ val song: CharSequence?,
+ val artworkIcon: Icon?,
+ val actionIcons: List<MediaAction>,
+ val actionsToShowInCompact: List<Int>,
+ val semanticActions: MediaButton?,
+ val token: MediaSession.Token?,
+ val clickIntent: PendingIntent?,
+ val device: MediaDeviceData?,
+ val playbackLocation: Int,
+ val isPlaying: Boolean?,
+ val appUid: Int,
+ val isExplicit: Boolean,
+ val resumeAction: Runnable? = null,
+ val resumeProgress: Double? = null
+ )
+}
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..9e2804e 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
@@ -1043,14 +1044,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 +1143,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 +1167,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 +1308,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/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/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/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 2dbe2aa..bf2aa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -20,7 +20,7 @@
import android.annotation.UserIdInt
import android.app.ActivityManager.RecentTaskInfo
import android.content.ComponentName
-import com.android.wm.shell.util.SplitBounds
+import com.android.wm.shell.shared.split.SplitBounds
data class RecentTask(
val taskId: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 01b1be9..82e58cc 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -23,7 +23,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index dd1fa76..bb4d894 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -36,10 +36,10 @@
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
import com.android.systemui.res.R
import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
import java.util.Optional
import javax.inject.Inject
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/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 0e127e3..83c3335 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -41,10 +41,11 @@
if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() && data.icon != null) {
icon = { data.icon }
} else {
- val defaultIconRes =
+ val iconRes =
if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
- iconRes = defaultIconRes
- icon = { resources.getDrawable(defaultIconRes, theme).asIcon() }
+ val icon = resources.getDrawable(iconRes, theme).asIcon()
+ this.iconRes = iconRes
+ this.icon = { icon }
}
activationState =
if (data.isActivated) {
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/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..6223ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -33,6 +33,8 @@
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
@@ -55,6 +57,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 +96,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 +150,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 +224,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 +301,7 @@
init()
}
}
+ lifecycleRegistry.addObserver(touchLifecycleLogger)
lifecycleRegistry.currentState = Lifecycle.State.CREATED
communalContainerView = containerView
@@ -328,6 +362,9 @@
backGestureInset
)
}
+ logger.d({ "Insets updated: $str1" }) {
+ str1 = containerView.systemGestureExclusionRects.toString()
+ }
}
}
@@ -343,6 +380,9 @@
),
{
anyBouncerShowing = it
+ if (hubShowing) {
+ logger.d({ "New value for anyBouncerShowing: $bool1" }) { bool1 = it }
+ }
updateTouchHandlingState()
}
)
@@ -396,7 +436,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 +450,7 @@
communalContainerWrapper = CommunalWrapper(containerView.context)
communalContainerWrapper?.addView(communalContainerView)
+ logger.d("Hub container initialized")
return communalContainerWrapper!!
}
@@ -446,6 +493,10 @@
(it.parent as ViewGroup).removeView(it)
communalContainerWrapper = null
}
+
+ lifecycleRegistry.removeObserver(touchLifecycleLogger)
+
+ logger.d("Hub container disposed")
}
/**
@@ -463,15 +514,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 +543,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 +613,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/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6a2c602..a16129b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -511,10 +511,12 @@
}
public int getTopPadding() {
+ SceneContainerFlag.assertInLegacyMode();
return mTopPadding;
}
public void setTopPadding(int topPadding) {
+ SceneContainerFlag.assertInLegacyMode();
mTopPadding = topPadding;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f26f840..d0c51bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -851,7 +851,7 @@
return; // the rest of the fields are not important in Flexiglass
}
- y = getTopPadding();
+ y = mAmbientState.getTopPadding();
drawDebugInfo(canvas, y, Color.RED, /* label= */ "getTopPadding() = " + y);
y = getLayoutHeight();
@@ -1231,9 +1231,11 @@
@Override
public void setStackTop(float stackTop) {
- mAmbientState.setStackTop(stackTop);
- // TODO(b/332574413): replace the following with using stackTop
- updateTopPadding(stackTop, isAddOrRemoveAnimationPending());
+ if (mAmbientState.getStackTop() != stackTop) {
+ mAmbientState.setStackTop(stackTop);
+ onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
+ setExpandedHeight(mExpandedHeight);
+ }
}
@Override
@@ -1386,28 +1388,30 @@
}
public int getTopPadding() {
- return mAmbientState.getTopPadding();
+ // TODO(b/332574413) replace all usages of getTopPadding()
+ if (SceneContainerFlag.isEnabled()) {
+ return (int) mAmbientState.getStackTop();
+ } else {
+ return mAmbientState.getTopPadding();
+ }
}
- private void setTopPadding(int topPadding, boolean animate) {
- if (getTopPadding() != topPadding) {
- mAmbientState.setTopPadding(topPadding);
- boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
- updateAlgorithmHeightAndPadding();
- updateContentHeight();
- if (mAmbientState.isOnKeyguard()
- && !mShouldUseSplitNotificationShade
- && mShouldSkipTopPaddingAnimationAfterFold) {
- mShouldSkipTopPaddingAnimationAfterFold = false;
- } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
- mTopPaddingNeedsAnimation = true;
- mNeedsAnimation = true;
- }
- updateStackPosition();
- requestChildrenUpdate();
- notifyHeightChangeListener(null, shouldAnimate);
- mAnimateNextTopPaddingChange = false;
+ private void onTopPaddingChanged(boolean animate) {
+ boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
+ updateAlgorithmHeightAndPadding();
+ updateContentHeight();
+ if (mAmbientState.isOnKeyguard()
+ && !mShouldUseSplitNotificationShade
+ && mShouldSkipTopPaddingAnimationAfterFold) {
+ mShouldSkipTopPaddingAnimationAfterFold = false;
+ } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+ mTopPaddingNeedsAnimation = true;
+ mNeedsAnimation = true;
}
+ updateStackPosition();
+ requestChildrenUpdate();
+ notifyHeightChangeListener(null, shouldAnimate);
+ mAnimateNextTopPaddingChange = false;
}
/**
@@ -1435,6 +1439,11 @@
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
+ // When scene container is active, we only want to recalculate stack heights.
+ if (SceneContainerFlag.isEnabled()) {
+ updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+ return;
+ }
float topOverscrollAmount = mShouldUseSplitNotificationShade
? getCurrentOverScrollAmount(true /* top */) : 0f;
final float endTopPosition = getTopPadding() + mExtraTopInsetForFullShadeTransition
@@ -1447,10 +1456,8 @@
if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
- if (!SceneContainerFlag.isEnabled()) {
- final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
- mAmbientState.setStackY(stackY);
- }
+ final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
+ mAmbientState.setStackY(stackY);
if (mOnStackYChanged != null) {
mOnStackYChanged.accept(listenerNeedsAnimation);
@@ -2708,6 +2715,7 @@
* @param animate whether to animate the change
*/
public void updateTopPadding(float qsHeight, boolean animate) {
+ SceneContainerFlag.assertInLegacyMode();
int topPadding = (int) qsHeight;
int minStackHeight = getLayoutMinHeightInternal();
if (topPadding + minStackHeight > getHeight()) {
@@ -2715,7 +2723,10 @@
} else {
mTopPaddingOverflow = 0;
}
- setTopPadding(topPadding, animate && !mKeyguardBypassEnabled);
+ if (mAmbientState.getTopPadding() != topPadding) {
+ mAmbientState.setTopPadding(topPadding);
+ onTopPaddingChanged(/* animate = */ animate && !mKeyguardBypassEnabled);
+ }
setExpandedHeight(mExpandedHeight);
}
@@ -4829,6 +4840,7 @@
}
public boolean isBelowLastNotification(float touchX, float touchY) {
+ SceneContainerFlag.assertInLegacyMode();
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
ExpandableView child = getChildAtIndex(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 55f0566..c25b30d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1235,6 +1235,7 @@
}
public boolean isBelowLastNotification(float x, float y) {
+ SceneContainerFlag.assertInLegacyMode();
return mView.isBelowLastNotification(x, y);
}
@@ -1328,6 +1329,7 @@
}
public int getTopPadding() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getTopPadding();
}
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/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/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 50efc21..5f6ad92 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -60,7 +60,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.onehanded.OneHanded;
@@ -70,6 +69,7 @@
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 0b7a3ed..6aecc0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -22,6 +22,7 @@
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.keyguardUpdateMonitor
import com.android.keyguard.trustManager
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -52,12 +54,15 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -113,6 +118,7 @@
powerInteractor,
fakeBiometricSettingsRepository,
trustManager,
+ { kosmos.sceneInteractor },
deviceEntryFaceAuthStatusInteractor,
)
}
@@ -279,6 +285,22 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainerEnabled_faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+ testScope.runTest {
+ underTest.start()
+
+ kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, false))
+ }
+
+ @Test
fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
testScope.runTest {
underTest.start()
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/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index fc7f693..d13419e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.dock.DockManagerFake
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
@@ -152,9 +153,7 @@
dockManager = DockManagerFake()
biometricSettingsRepository = FakeBiometricSettingsRepository()
val featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
- }
+ FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
val keyguardInteractor = withDeps.keyguardInteractor
@@ -223,6 +222,7 @@
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManager,
pulsingGestureListener = kosmos.pulsingGestureListener,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
)
underTest =
KeyguardBottomAreaViewModel(
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/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index bdee936..fd53b5ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -38,19 +38,28 @@
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Bundle
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
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.dump.DumpManager
+import com.android.systemui.flags.Flags.MEDIA_REMOTE_RESUME
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_RECOMMENDATIONS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_SESSIONS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
@@ -58,16 +67,21 @@
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.MediaControllerFactory
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
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.After
import org.junit.Before
import org.junit.Rule
@@ -91,6 +105,8 @@
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
@@ -111,13 +127,13 @@
return Mockito.anyObject<T>()
}
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class LegacyMediaDataManagerImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock lateinit var mediaControllerFactory: MediaControllerFactory
@Mock lateinit var controller: MediaController
@Mock lateinit var transportControls: MediaController.TransportControls
@Mock lateinit var playbackInfo: MediaController.PlaybackInfo
@@ -136,7 +152,6 @@
@Mock lateinit var mediaDataFilter: LegacyMediaDataFilterImpl
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
- @Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@@ -144,7 +159,6 @@
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
lateinit var validRecommendationList: List<SmartspaceAction>
@Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
- @Mock private lateinit var mediaFlags: MediaFlags
@Mock private lateinit var logger: MediaUiEventLogger
lateinit var mediaDataManager: LegacyMediaDataManagerImpl
lateinit var mediaNotification: StatusBarNotification
@@ -159,6 +173,26 @@
@Mock private lateinit var ugm: IUriGrantsManager
@Mock private lateinit var imageSource: ImageDecoder.Source
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.progressionOf(
+ Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER
+ )
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = testKosmos()
+ private val testDispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
+ private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+ private val activityStarter = kosmos.activityStarter
+ private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
private val originalSmartspaceSetting =
@@ -188,12 +222,16 @@
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
1
)
+
mediaDataManager =
LegacyMediaDataManagerImpl(
context = context,
backgroundExecutor = backgroundExecutor,
+ backgroundDispatcher = testDispatcher,
uiExecutor = uiExecutor,
foregroundExecutor = foregroundExecutor,
+ mainDispatcher = testDispatcher,
+ applicationScope = testScope,
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
@@ -209,10 +247,11 @@
useQsMediaPlayer = true,
systemClock = clock,
tunerService = tunerService,
- mediaFlags = mediaFlags,
+ mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
keyguardUpdateMonitor = keyguardUpdateMonitor,
+ mediaDataLoader = { kosmos.mediaDataLoader },
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -248,7 +287,7 @@
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
- whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+ mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -278,10 +317,11 @@
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, false)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, false)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
}
@@ -310,49 +350,51 @@
}
@Test
- fun testsetInactive_resume_dismissesMedia() {
- // WHEN resume controls are present, and time out
- val desc =
- MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
-
- backgroundExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
+ fun testsetInactive_resume_dismissesMedia() =
+ testScope.runTest {
+ // WHEN resume controls are present, and time out
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
)
- mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
- verify(logger)
- .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+ runCurrent()
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
- // THEN it is removed and listeners are informed
- foregroundExecutor.advanceClockToLast()
- foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
- }
+ mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
+ verify(logger)
+ .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+
+ // THEN it is removed and listeners are informed
+ foregroundExecutor.advanceClockToLast()
+ foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
+ }
@Test
fun testLoadsMetadataOnBackground() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
}
@Test
@@ -370,8 +412,7 @@
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -389,8 +430,7 @@
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -417,11 +457,9 @@
@Test
fun testOnMetaDataLoaded_conservesActiveFlag() {
- whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -465,8 +503,7 @@
}
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -552,8 +589,7 @@
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -583,8 +619,7 @@
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -625,8 +660,7 @@
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then the media control is added using the notification's title
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -734,8 +768,7 @@
// GIVEN that the manager has two notifications with resume actions
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+ testScope.assertRunAllReady(foreground = 2, background = 2)
verify(listener)
.onMediaDataLoaded(
@@ -822,7 +855,7 @@
@Test
fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a notification with a resume action, but is not local
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -853,7 +886,7 @@
@Test
fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a remote cast notification
addNotificationAndLoad(remoteCastNotification)
@@ -972,7 +1005,7 @@
@Test
fun testAddResumptionControls_hasPartialProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with partial progress
val progress = 0.5
@@ -999,7 +1032,7 @@
@Test
fun testAddResumptionControls_hasNotPlayedProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have not been played
val extras =
@@ -1024,7 +1057,7 @@
@Test
fun testAddResumptionControls_hasFullProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with progress info
val extras =
@@ -1050,7 +1083,7 @@
@Test
fun testAddResumptionControls_hasNoExtras() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that do not have any extras
val desc =
@@ -1068,7 +1101,7 @@
@Test
fun testAddResumptionControls_hasEmptyTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have empty title
val desc =
@@ -1087,8 +1120,7 @@
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1102,7 +1134,7 @@
@Test
fun testAddResumptionControls_hasBlankTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have a blank title
val desc =
@@ -1121,8 +1153,7 @@
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1189,8 +1220,7 @@
mediaDataManager.onNotificationAdded(KEY, notif)
// THEN it still loads
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1307,7 +1337,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1333,7 +1363,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
val extras =
Bundle().apply {
putString("package_name", PACKAGE_NAME)
@@ -1367,7 +1397,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1399,7 +1429,7 @@
@Test
fun testSetRecommendationInactive_notifiesListeners() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1479,8 +1509,7 @@
fun testOnMediaDataTimedOut_updatesLastActiveTime() {
// GIVEN that the manager has a notification
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// WHEN the notification times out
clock.advanceTime(100)
@@ -1588,8 +1617,7 @@
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_COMPACT_ACTIONS are actually set
verify(listener)
@@ -1624,8 +1652,7 @@
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
verify(listener)
@@ -1644,7 +1671,7 @@
@Test
fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action"
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
whenever(controller.playbackState).thenReturn(null)
val notifWithAction =
@@ -1659,8 +1686,7 @@
}
mediaDataManager.onNotificationAdded(KEY, notifWithAction)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1679,7 +1705,7 @@
@Test
fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions =
PlaybackState.ACTION_PLAY or
PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1723,7 +1749,7 @@
@Test
fun testPlaybackActions_noPrevNext_usesCustom() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1755,7 +1781,7 @@
@Test
fun testPlaybackActions_connecting() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder =
PlaybackState.Builder()
@@ -1776,7 +1802,7 @@
@Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1814,7 +1840,7 @@
@Test
fun testPlaybackActions_playPause_hasButton() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY_PAUSE
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1851,8 +1877,7 @@
// update to remote cast
mediaDataManager.onNotificationAdded(KEY, remoteCastNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(logger)
.logPlaybackLocationChange(
anyInt(),
@@ -1914,7 +1939,7 @@
@Test
fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
whenever(controller.playbackState).thenReturn(state)
@@ -1935,46 +1960,48 @@
}
@Test
- fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
- val desc =
- MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- val state =
- PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
- .setActions(PlaybackState.ACTION_PLAY_PAUSE)
- .build()
+ fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() =
+ testScope.runTest {
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ val state =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+ .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+ .build()
- // Add resumption controls in order to have semantic actions.
- // To make sure that they are not null after changing state.
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- backgroundExecutor.runAllReady()
- foregroundExecutor.runAllReady()
-
- stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
-
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(PACKAGE_NAME),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
+ // Add resumption controls in order to have semantic actions.
+ // To make sure that they are not null after changing state.
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
)
- assertThat(mediaDataCaptor.value.isPlaying).isFalse()
- assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
- }
+ runCurrent()
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+
+ stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(PACKAGE_NAME),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+ }
@Test
fun testPlaybackStateNull_Pause_keyExists_callsListener() {
@@ -2036,7 +2063,7 @@
@Test
fun testRetain_notifPlayer_notifRemoved_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added, times out, and then removed
addNotificationAndLoad()
@@ -2066,7 +2093,7 @@
@Test
fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and times out
addNotificationAndLoad()
@@ -2084,7 +2111,7 @@
@Test
fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and then removed, without timing out
addNotificationAndLoad()
@@ -2101,7 +2128,7 @@
@Test
fun testRetain_canResume_removeWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control that supports resumption is added
addNotificationAndLoad()
@@ -2133,8 +2160,8 @@
@Test
fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2153,8 +2180,8 @@
@Test
fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2187,8 +2214,8 @@
@Test
fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2207,8 +2234,8 @@
@Test
fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2241,7 +2268,7 @@
@Test
fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2268,7 +2295,7 @@
@Test
fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2287,7 +2314,7 @@
@Test
fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2320,8 +2347,8 @@
@Test
fun testSessionDestroyed_noNotificationKey_stillRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
// When a notiifcation is added and then removed before it is fully processed
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
@@ -2392,6 +2419,23 @@
assertThat(mediaDataCaptor.value.artwork).isNull()
}
+ private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
+ runCurrent()
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ // It doesn't make much sense to count tasks when we use coroutines in loader
+ // so this check is skipped in that scenario.
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ } else {
+ if (background > 0) {
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(background)
+ }
+ if (foreground > 0) {
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(foreground)
+ }
+ }
+ }
+
/** Helper function to add a basic media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
addNotificationAndLoad(mediaNotification)
@@ -2400,8 +2444,7 @@
/** Helper function to add the given notification and capture the resulting MediaData */
private fun addNotificationAndLoad(sbn: StatusBarNotification) {
mediaDataManager.onNotificationAdded(KEY, sbn)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -2435,8 +2478,8 @@
pendingIntent,
packageName
)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
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..2cc8cc7 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,13 @@
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.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 +75,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 +86,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
@@ -146,7 +154,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,7 +192,9 @@
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
@@ -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/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index f531a3f..3e3aa4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,9 +16,9 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index 8fbd3c8..69b7b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -29,8 +29,8 @@
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import java.util.Optional
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..90c70f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -58,6 +58,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 +141,8 @@
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
}
testableLooper = TestableLooper.get(this)
@@ -186,7 +188,8 @@
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
// First call succeeds.
@@ -214,7 +217,8 @@
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
@@ -237,7 +241,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
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/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/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
similarity index 76%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
index c968e80..6251ae9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package android.app
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.statusBarManager by Kosmos.Fixture { mock<StatusBarManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index b9be04d..3dfe0ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -33,6 +33,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +58,7 @@
powerInteractor = powerInteractor,
biometricSettingsRepository = biometricSettingsRepository,
trustManager = trustManager,
+ sceneInteractor = { sceneInteractor },
deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
)
}
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/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
similarity index 80%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
index c968e80..f5a05b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.systemui.haptics.msdl
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+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/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 73799b6..769612c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -20,6 +20,7 @@
import android.view.accessibility.accessibilityManagerWrapper
import com.android.internal.logging.uiEventLogger
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
@@ -38,5 +39,6 @@
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManagerWrapper,
pulsingGestureListener = pulsingGestureListener,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
)
}
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/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
new file mode 100644
index 0000000..a5690a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.app.statusBarManager
+import android.content.testableContext
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.mediaDataLoader by
+ Kosmos.Fixture {
+ MediaDataLoader(
+ testableContext,
+ testDispatcher,
+ testScope,
+ activityStarter,
+ fakeMediaControllerFactory,
+ mediaFlags,
+ imageLoader,
+ statusBarManager
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index cc1ad1f..2127a88 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -28,7 +28,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
import com.android.systemui.plugins.activityStarter
@@ -46,7 +46,7 @@
uiExecutor = fakeExecutor,
foregroundExecutor = fakeExecutor,
handler = fakeExecutorHandler,
- mediaControllerFactory = mediaControllerFactory,
+ mediaControllerFactory = fakeMediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
index b98f557..c479ce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
@@ -22,8 +22,8 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.localMediaManagerFactory
-import com.android.systemui.media.controls.util.mediaControllerFactory
import com.android.systemui.media.muteawait.mediaMuteAwaitConnectionManagerFactory
import com.android.systemui.statusbar.policy.configurationController
@@ -31,7 +31,7 @@
Kosmos.Fixture {
MediaDeviceManager(
context = applicationContext,
- controllerFactory = mediaControllerFactory,
+ controllerFactory = fakeMediaControllerFactory,
localMediaManagerFactory = localMediaManagerFactory,
mr2manager = { MediaRouter2Manager.getInstance(applicationContext) },
muteAwaitConnectionManagerFactory = mediaMuteAwaitConnectionManagerFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
index 6ec6378..b7660e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
@@ -19,7 +19,7 @@
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.util.time.systemClock
@@ -27,7 +27,7 @@
val Kosmos.mediaTimeoutListener by
Kosmos.Fixture {
MediaTimeoutListener(
- mediaControllerFactory = mediaControllerFactory,
+ mediaControllerFactory = fakeMediaControllerFactory,
mainExecutor = fakeExecutor,
logger = MediaTimeoutLogger(logcatLogBuffer("MediaTimeoutLogBuffer")),
statusBarStateController = statusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
new file mode 100644
index 0000000..7f8348e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.MediaSession.Token
+
+class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
+
+ private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+
+ override fun create(token: MediaSession.Token): android.media.session.MediaController {
+ if (token !in mediaControllersForToken) {
+ super.create(token)
+ }
+ return mediaControllersForToken[token]!!
+ }
+
+ fun setControllerForToken(token: Token, mediaController: MediaController) {
+ mediaControllersForToken[token] = mediaController
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
index 1ce6e82..7ee58fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
@@ -19,4 +19,5 @@
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-val Kosmos.mediaControllerFactory by Kosmos.Fixture { MediaControllerFactory(applicationContext) }
+val Kosmos.fakeMediaControllerFactory by
+ Kosmos.Fixture { FakeMediaControllerFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index ae8b411..f84c3bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.scene.domain.resolver.sceneFamilyResolvers
import com.android.systemui.scene.shared.logger.sceneLogger
-val Kosmos.sceneInteractor by
+val Kosmos.sceneInteractor: SceneInteractor by
Kosmos.Fixture {
SceneInteractor(
applicationScope = applicationCoroutineScope,
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..c71b99f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -331,6 +331,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/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/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/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 1298023..9a11a8a 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -42,12 +42,15 @@
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";
+
// @GuardedBy("sLock")
private static boolean sIntegrityChecked = false;
@@ -178,7 +181,7 @@
*/
public static String getRavenwoodRuntimePath() {
ensureOnRavenwood();
- return RAVEWOOD_RUNTIME_PATH;
+ return RAVENWOOD_RUNTIME_PATH;
}
private static String getRavenwoodRuntimePathInternal() {
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/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/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/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..930fb39 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
@@ -14663,6 +14702,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/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/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index ac56043..b35a0a7 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -71,6 +71,7 @@
import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
+import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
import static libcore.io.IoUtils.closeQuietly;
@@ -1388,14 +1389,22 @@
@NonNull
private static NetworkStats removeEmptyEntries(NetworkStats stats) {
- NetworkStats ret = new NetworkStats(0, 1);
+ final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
for (NetworkStats.Entry e : stats) {
if (e.getRxBytes() != 0 || e.getRxPackets() != 0 || e.getTxBytes() != 0
|| e.getTxPackets() != 0 || e.getOperations() != 0) {
- ret = ret.addEntry(e);
+ entries.add(e);
}
}
- return ret;
+ if (isAddEntriesSupported()) {
+ return new NetworkStats(0, entries.size()).addEntries(entries);
+ } else {
+ NetworkStats outputStats = new NetworkStats(0L, 1);
+ for (NetworkStats.Entry e : entries) {
+ outputStats = outputStats.addEntry(e);
+ }
+ return outputStats;
+ }
}
private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@@ -1720,11 +1729,19 @@
@NonNull
private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
@NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
- NetworkStats ret = new NetworkStats(0, 1);
+ final ArrayList<NetworkStats.Entry> entries = new ArrayList();
for (NetworkStats.Entry e : stats) {
- ret = ret.addEntry(slicer.apply(e));
+ entries.add(slicer.apply(e));
}
- return ret;
+ if (isAddEntriesSupported()) {
+ return new NetworkStats(0, entries.size()).addEntries(entries);
+ } else {
+ NetworkStats outputStats = new NetworkStats(0L, 1);
+ for (NetworkStats.Entry e : entries) {
+ outputStats = outputStats.addEntry(e);
+ }
+ return outputStats;
+ }
}
private void registerWifiBytesTransferBackground() {
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
index de58852..0318bdd 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
@@ -24,6 +24,9 @@
import android.app.usage.NetworkStats;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.stats.Flags;
+
+import java.util.ArrayList;
/**
* Utility methods for accessing {@link android.net.NetworkStats}.
@@ -35,12 +38,21 @@
*/
public static android.net.NetworkStats fromPublicNetworkStats(
NetworkStats publiceNetworkStats) {
- android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+ final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
while (publiceNetworkStats.hasNextBucket()) {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
publiceNetworkStats.getNextBucket(bucket);
- final android.net.NetworkStats.Entry entry = fromBucket(bucket);
- stats = stats.addEntry(entry);
+ entries.add(fromBucket(bucket));
+ }
+ android.net.NetworkStats stats = new android.net.NetworkStats(0L, 1);
+ // The new API is only supported on devices running the mainline version of `NetworkStats`.
+ // It should always be used when available for memory efficiency.
+ if (isAddEntriesSupported()) {
+ stats = stats.addEntries(entries);
+ } else {
+ for (android.net.NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
}
return stats;
}
@@ -106,4 +118,8 @@
}
return 0;
}
+
+ public static boolean isAddEntriesSupported() {
+ return Flags.netstatsUseAddEntries();
+ }
}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index f360837..afea303 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,6 +1,20 @@
package: "com.android.server.stats"
container: "system"
+# Note: To ensure compatibility across all release configurations, initiate the ramp-up process
+# only after the 'com.android.net.flags.netstats_add_entries' flag has been fully deployed.
+# This flag provides the necessary API from the Connectivity module.
+# The flag was added because the flag 'com.android.net.flags.netstats_add_entries' for API
+# is already being rolled out, and modifying behavior during an active rollout might
+# lead to unwanted issues.
+flag {
+ name: "netstats_use_add_entries"
+ namespace: "statsd"
+ description: "Use NetworkStats#addEntries to reduce memory footprint"
+ bug: "335680025"
+ is_fixed_read_only: true
+}
+
flag {
name: "add_mobile_bytes_transfer_by_proc_state_puller"
namespace: "statsd"
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6bf70f0..47d4740 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;
@@ -4942,9 +4941,8 @@
newIntents.add(intent);
}
- final boolean isSleeping() {
- final Task rootTask = getRootTask();
- return rootTask != null ? rootTask.shouldSleepActivities() : mAtmService.isSleepingLocked();
+ boolean isSleeping() {
+ return task != null ? task.shouldSleepActivities() : mAtmService.isSleepingLocked();
}
/**
@@ -4968,7 +4966,7 @@
final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
callerToken);
boolean unsent = true;
- final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
+ final boolean isTopActivityWhileSleeping = isSleeping() && isTopRunningActivity();
// We want to immediately deliver the intent to the activity if:
// - It is currently resumed or paused. i.e. it is currently visible to the user and we want
@@ -5543,10 +5541,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
@@ -5700,8 +5695,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,
@@ -5723,7 +5717,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);
}
@@ -5835,7 +5829,7 @@
void setState(State state, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
- this, getState(), state, reason);
+ this, mState, state, reason);
if (state == mState) {
// No need to do anything if state doesn't change.
@@ -6160,7 +6154,7 @@
// Now for any activities that aren't visible to the user, make sure they no longer are
// keeping the screen frozen.
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState());
+ Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + mState);
}
try {
final boolean canEnterPictureInPicture = checkEnterPictureInPictureState(
@@ -6176,7 +6170,7 @@
}
setVisibility(false);
- switch (getState()) {
+ switch (mState) {
case STOPPING:
case STOPPED:
// Reset the flag indicating that an app can enter picture-in-picture once the
@@ -7701,7 +7695,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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2d8e3db..0f108c5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3617,6 +3617,11 @@
final long token = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
+ boolean isPowerModePreApplied = false;
+ if (mPowerModeReasons == 0) {
+ startPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+ isPowerModePreApplied = true;
+ }
// Keyguard asked us to clear the home task snapshot before going away, so do that.
if ((flags & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
mActivityClientController.invalidateHomeTaskSnapshot(null /* token */);
@@ -3625,9 +3630,19 @@
mDemoteTopAppReasons |= DEMOTE_TOP_REASON_DURING_UNLOCKING;
}
- mRootWindowContainer.forAllDisplays(displayContent -> {
- mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
- });
+ boolean foundResumed = false;
+ for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+ final DisplayContent dc = mRootWindowContainer.getChildAt(i);
+ final boolean wasNoResumed = dc.mFocusedApp == null
+ || !dc.mFocusedApp.isState(RESUMED);
+ mKeyguardController.keyguardGoingAway(dc.mDisplayId, flags);
+ if (wasNoResumed && dc.mFocusedApp != null && dc.mFocusedApp.isState(RESUMED)) {
+ foundResumed = true;
+ }
+ }
+ if (isPowerModePreApplied && !foundResumed) {
+ endPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+ }
}
WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
if (wallpaperManagerInternal != 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/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f23dafe..ddbfd70 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -49,7 +49,6 @@
import static java.lang.Integer.MAX_VALUE;
import android.annotation.Nullable;
-import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
import android.os.IBinder;
@@ -111,7 +110,7 @@
* draw the live-tile above the recents activity, we also need to provide that activity as a
* z-layering reference so that we can place the recents input consumer above it.
*/
- private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
+ private WeakReference<Task> mActiveRecentsTask = null;
private WeakReference<Task> mActiveRecentsLayerRef = null;
private class UpdateInputWindows implements Runnable {
@@ -388,13 +387,13 @@
/**
* Inform InputMonitor when recents is active so it can enable the recents input consumer.
- * @param activity The active recents activity. {@code null} means recents is not active.
+ * @param task The active recents task. {@code null} means recents is not active.
* @param layer A task whose Z-layer is used as a reference for how to sort the consumer.
*/
- void setActiveRecents(@Nullable ActivityRecord activity, @Nullable Task layer) {
- final boolean clear = activity == null;
- final boolean wasActive = mActiveRecentsActivity != null && mActiveRecentsLayerRef != null;
- mActiveRecentsActivity = clear ? null : new WeakReference<>(activity);
+ void setActiveRecents(@Nullable Task task, @Nullable Task layer) {
+ final boolean clear = task == null;
+ final boolean wasActive = mActiveRecentsTask != null && mActiveRecentsLayerRef != null;
+ mActiveRecentsTask = clear ? null : new WeakReference<>(task);
mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer);
if (clear && wasActive) {
setUpdateInputWindowsNeededLw();
@@ -415,7 +414,7 @@
if (recentsAnimationInputConsumer != null && focus != null) {
// Apply recents input consumer when the focusing window is in recents animation.
final boolean shouldApplyRecentsInputConsumer =
- getWeak(mActiveRecentsActivity) != null && focus.inTransition()
+ getWeak(mActiveRecentsTask) != null && focus.inTransition()
// only take focus from the recents activity to avoid intercepting
// events before the gesture officially starts.
&& focus.isActivityTypeHomeOrRecents();
@@ -564,7 +563,6 @@
private boolean mAddRecentsAnimationInputConsumerHandle;
private boolean mInDrag;
- private final Rect mTmpRect = new Rect();
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -581,18 +579,15 @@
resetInputConsumers(mInputTransaction);
// Update recents input consumer layer if active
- final ActivityRecord activeRecents = getWeak(mActiveRecentsActivity);
+ final Task activeRecents = getWeak(mActiveRecentsTask);
if (mAddRecentsAnimationInputConsumerHandle && activeRecents != null
&& activeRecents.getSurfaceControl() != null) {
WindowContainer layer = getWeak(mActiveRecentsLayerRef);
layer = layer != null ? layer : activeRecents;
// Handle edge-case for SUW where windows don't exist yet
if (layer.getSurfaceControl() != null) {
- final WindowState targetAppMainWindow = activeRecents.findMainWindow();
- if (targetAppMainWindow != null) {
- targetAppMainWindow.getBounds(mTmpRect);
- mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
- }
+ mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
+ activeRecents.getBounds());
mRecentsAnimationInputConsumer.show(mInputTransaction, layer);
mAddRecentsAnimationInputConsumerHandle = false;
}
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 d6bd55f..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() {
@@ -6196,26 +6194,6 @@
ActivityOptions.abort(options);
}
- boolean shouldSleepActivities() {
- final DisplayContent display = mDisplayContent;
- final boolean isKeyguardGoingAway = (mDisplayContent != null)
- ? mDisplayContent.isKeyguardGoingAway()
- : mRootWindowContainer.getDefaultDisplay().isKeyguardGoingAway();
-
- // Do not sleep activities in this root task if we're marked as focused and the keyguard
- // is in the process of going away.
- if (isKeyguardGoingAway && isFocusedRootTaskOnDisplay()
- // Avoid resuming activities on secondary displays since we don't want bubble
- // activities to be resumed while bubble is still collapsed.
- // TODO(b/113840485): Having keyguard going away state for secondary displays.
- && display != null
- && display.isDefaultDisplay) {
- return false;
- }
-
- return display != null ? display.isSleeping() : mAtmService.isSleepingLocked();
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c8139fa..f58b322 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2145,8 +2145,22 @@
}
boolean shouldSleepActivities() {
- final Task task = getRootTask();
- return task != null && task.shouldSleepActivities();
+ final DisplayContent dc = mDisplayContent;
+ if (dc == null) {
+ return mAtmService.isSleepingLocked();
+ }
+ if (!dc.isSleeping()) {
+ return false;
+ }
+ // In case the unlocking order is keyguard-going-away -> screen-turning-on (display is
+ // sleeping by screen-off-token which may be notified to release from power manager's
+ // thread), keep the activities resume-able to avoid extra activity lifecycle when
+ // performing keyguard-going-away. This only applies to default display because currently
+ // the per-display keyguard-going-away state is assigned from a global signal.
+ if (!dc.isDefaultDisplay || !dc.isKeyguardGoingAway()) {
+ return true;
+ }
+ return !shouldBeVisible(null /* starting */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e76e94d..e25db7e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1453,7 +1453,7 @@
// Clean up input monitors (for recents)
final DisplayContent dc =
mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
- dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
+ dc.getInputMonitor().setActiveRecents(null /* task */, null /* layer */);
dc.getInputMonitor().updateInputWindowsLw(false /* force */);
}
if (mTransientLaunches != null) {
@@ -2249,7 +2249,7 @@
// Recents has an input-consumer to grab input from the "live tile" app. Set that up here
final InputConsumerImpl recentsAnimationInputConsumer =
dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
- ActivityRecord recentsActivity = null;
+ Task recentsTask = null;
if (recentsAnimationInputConsumer != null) {
// Find the top-most going-away task and the recents activity. The top-most
// is used as layer reference while the recents is used for registering the consumer
@@ -2264,20 +2264,20 @@
final int activityType = taskInfo.topActivityType;
final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_RECENTS;
- if (isRecents && recentsActivity == null) {
- recentsActivity = task.getTopVisibleActivity();
+ if (isRecents && recentsTask == null) {
+ recentsTask = task;
} else if (!isRecents && topNonRecentsTask == null) {
topNonRecentsTask = task;
}
}
- if (recentsActivity != null && topNonRecentsTask != null) {
+ if (recentsTask != null && topNonRecentsTask != null) {
recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
topNonRecentsTask.getBounds());
- dc.getInputMonitor().setActiveRecents(recentsActivity, topNonRecentsTask);
+ dc.getInputMonitor().setActiveRecents(recentsTask, topNonRecentsTask);
}
}
- if (recentsActivity == null) {
+ if (recentsTask == null) {
// No recents activity on `dc`, its probably on a different display.
return;
}
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 4b91e27..9b55ed2 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";
}
@@ -8947,6 +8945,22 @@
// display it's on to the top since that window won't be able to get focus anyway.
return;
}
+
+ final ActivityRecord touchedApp = t.getActivityRecord();
+ if (touchedApp != null && touchedApp.getTask() != null) {
+ final ActivityRecord top = touchedApp.getTask().topRunningActivity();
+ if (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
+ // pointer-down-outside-focus event is delayed (after new activity started on top).
+ // In that case, drop the event to prevent changing focus to a background activity.
+ Slog.w(TAG, "onPointerDownOutsideFocusLocked, drop event because " + touchedApp
+ + " is occluded and should not be focused.");
+ return;
+ }
+ }
+
clearPointerDownOutsideFocusRunnable();
if (shouldDelayTouchOutside(t)) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81bce18..c8e4b0a 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;
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 73ecbb4..dc048ef 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -89,6 +89,7 @@
PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
.build();
register(params);
+ Log.i(TAG, "Registered with perfetto service");
}
@Override
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 0787058..2c785049 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/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/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/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/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index d8d5729..ea175a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -24,6 +24,8 @@
import android.graphics.PixelFormat;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
@@ -72,6 +74,7 @@
* Checks that scheduling with all the state set and manually triggering the show does succeed.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
@@ -99,6 +102,7 @@
* all the state becomes available.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_noInitialState() {
final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
@@ -126,6 +130,7 @@
* does continue and succeed when the runnable is started.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedAfterPrepareSurfaces() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
@@ -158,6 +163,7 @@
* when the surface placement happens.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedSurfacePlacement() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0dc56f8..964264d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,6 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -202,6 +203,11 @@
getController().onImeControlTargetChanged(base);
base.setRequestedVisibleTypes(ime(), ime());
getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // to set the serverVisibility, the IME needs to be drawn and onPostLayout be called.
+ mImeWindow.mWinAnimator.mDrawState = HAS_DRAWN;
+ getController().onPostLayout();
+ }
// Send our spy window (app) into the system so that we can detect the invocation.
final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -500,6 +506,12 @@
getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // The IME is only set to shown, after onPostLayout is called and all preconditions
+ // (serverVisible, no givenInsetsPending, etc.) are fulfilled
+ getController().getImeSourceProvider().onPostLayout();
+ }
+
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
assertNotNull(app.getInsetsState().peekSource(ID_IME));
verify(app, atLeastOnce()).notifyInsetsChanged();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 9007733..6adf0fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1260,25 +1260,40 @@
@Test
public void testShouldSleepActivities() {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ task.mDisplayContent = mock(DisplayContent.class);
// When focused activity and keyguard is going away, we should not sleep regardless
// of the display state, but keyguard-going-away should only take effects on default
- // display since there is no keyguard on secondary displays (yet).
- verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+ // display because the keyguard-going-away state of secondary displays are already the
+ // same as default display.
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
- verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
// When not the focused root task, defer to display sleeping state.
- verifyShouldSleepActivities(false /* focusedRootTask */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, false /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
// If keyguard is going away, defer to the display sleeping state.
- verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
- verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
}
+ private static void verifyShouldSleepActivities(Task task, boolean isVisibleTask,
+ boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
+ boolean expected) {
+ final DisplayContent display = task.mDisplayContent;
+ display.isDefaultDisplay = isDefaultDisplay;
+ doReturn(keyguardGoingAway).when(display).isKeyguardGoingAway();
+ doReturn(displaySleeping).when(display).isSleeping();
+ doReturn(isVisibleTask).when(task).shouldBeVisible(null /* starting */);
+
+ assertEquals(expected, task.shouldSleepActivities());
+ }
+
@Test
public void testNavigateUpTo() {
final ActivityStartController controller = mock(ActivityStartController.class);
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/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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3e8b326..7481daa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1777,8 +1777,8 @@
/**
* Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
- * to indicate user to decide whether current SIM should be preferred for all
- * data / voice / sms. {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
+ * to indicate the current SIM should be preferred for all data / voice / sms.
+ * {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
* which subscription should be the default subscription.
* @hide
*/
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")
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index b0f68f7..f68ae2c 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -98,6 +98,18 @@
}
@Override
+ public void onServiceDisconnected() {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onServiceDisconnected());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
if (mCallback != null) {
final long token = Binder.clearCallingIdentity();
@@ -247,13 +259,13 @@
mService = null;
synchronized (mProxyDataLock) {
if (!mCallbackProxyCache.isEmpty()) {
- mCallbackProxyCache.keySet().forEach(
- SharedConnectivityClientCallback::onServiceDisconnected);
+ mCallbackProxyCache.values().forEach(
+ SharedConnectivityCallbackProxy::onServiceDisconnected);
mCallbackProxyCache.clear();
}
if (!mProxyMap.isEmpty()) {
- mProxyMap.keySet().forEach(
- SharedConnectivityClientCallback::onServiceDisconnected);
+ mProxyMap.values().forEach(
+ SharedConnectivityCallbackProxy::onServiceDisconnected);
mProxyMap.clear();
}
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
index 521f943..7b892af 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -32,4 +32,5 @@
oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
oneway void onServiceConnected();
+ oneway void onServiceDisconnected();
}