Merge "Jank CUJ: Maximizing Non-Resizable App" into main
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index a50425e..db3de62 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -40,8 +40,8 @@
public ExecuteAppFunctionRequest createFromParcel(Parcel parcel) {
String targetPackageName = parcel.readString8();
String functionIdentifier = parcel.readString8();
- GenericDocument parameters;
- parameters = GenericDocument.createFromParcel(parcel);
+ GenericDocumentWrapper parameters = GenericDocumentWrapper
+ .CREATOR.createFromParcel(parcel);
Bundle extras = parcel.readBundle(Bundle.class.getClassLoader());
return new ExecuteAppFunctionRequest(
targetPackageName, functionIdentifier, extras, parameters);
@@ -75,17 +75,17 @@
*
* <p>The document may have missing parameters. Developers are advised to implement defensive
* handling measures.
- *
+ * <p>
* TODO(b/357551503): Document how function parameters can be obtained for function execution
*/
@NonNull
- private final GenericDocument mParameters;
+ private final GenericDocumentWrapper mParameters;
private ExecuteAppFunctionRequest(
@NonNull String targetPackageName,
@NonNull String functionIdentifier,
@NonNull Bundle extras,
- @NonNull GenericDocument parameters) {
+ @NonNull GenericDocumentWrapper parameters) {
mTargetPackageName = Objects.requireNonNull(targetPackageName);
mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
mExtras = Objects.requireNonNull(extras);
@@ -117,7 +117,7 @@
*/
@NonNull
public GenericDocument getParameters() {
- return mParameters;
+ return mParameters.getValue();
}
/**
@@ -152,7 +152,8 @@
@NonNull
private Bundle mExtras = Bundle.EMPTY;
@NonNull
- private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+ private GenericDocument mParameters =
+ new GenericDocument.Builder<>("", "", "").build();
public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
mTargetPackageName = Objects.requireNonNull(targetPackageName);
@@ -173,7 +174,8 @@
*/
@NonNull
public Builder setParameters(@NonNull GenericDocument parameters) {
- mParameters = Objects.requireNonNull(parameters);
+ Objects.requireNonNull(parameters);
+ mParameters = parameters;
return this;
}
@@ -183,7 +185,8 @@
@NonNull
public ExecuteAppFunctionRequest build() {
return new ExecuteAppFunctionRequest(
- mTargetPackageName, mFunctionIdentifier, mExtras, mParameters);
+ mTargetPackageName, mFunctionIdentifier, mExtras,
+ new GenericDocumentWrapper(mParameters));
}
}
}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 872327d..9fb3375 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -41,13 +41,16 @@
new Creator<ExecuteAppFunctionResponse>() {
@Override
public ExecuteAppFunctionResponse createFromParcel(Parcel parcel) {
- GenericDocument result =
- Objects.requireNonNull(GenericDocument.createFromParcel(parcel));
+ GenericDocumentWrapper resultWrapper =
+ Objects.requireNonNull(
+ GenericDocumentWrapper
+ .CREATOR.createFromParcel(parcel));
Bundle extras = Objects.requireNonNull(
parcel.readBundle(Bundle.class.getClassLoader()));
int resultCode = parcel.readInt();
String errorMessage = parcel.readString8();
- return new ExecuteAppFunctionResponse(result, extras, resultCode, errorMessage);
+ return new ExecuteAppFunctionResponse(
+ resultWrapper, extras, resultCode, errorMessage);
}
@Override
@@ -127,7 +130,7 @@
* <p>See {@link #getResultDocument} for more information on extracting the return value.
*/
@NonNull
- private final GenericDocument mResultDocument;
+ private final GenericDocumentWrapper mResultDocumentWrapper;
/**
* Returns the additional metadata data relevant to this function execution response.
@@ -135,17 +138,30 @@
@NonNull
private final Bundle mExtras;
- private ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument,
+ private ExecuteAppFunctionResponse(@NonNull GenericDocumentWrapper resultDocumentWrapper,
@NonNull Bundle extras,
@ResultCode int resultCode,
@Nullable String errorMessage) {
- mResultDocument = Objects.requireNonNull(resultDocument);
+ mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper);
mExtras = Objects.requireNonNull(extras);
mResultCode = resultCode;
mErrorMessage = errorMessage;
}
/**
+ * Returns result codes from throwable.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ static @ResultCode int getResultCode(@NonNull Throwable t) {
+ if (t instanceof IllegalArgumentException) {
+ return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+ }
+ return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+ }
+
+ /**
* Returns a generic document containing the return value of the executed function.
*
* <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.</p>
@@ -166,7 +182,7 @@
*/
@NonNull
public GenericDocument getResultDocument() {
- return mResultDocument;
+ return mResultDocumentWrapper.getValue();
}
/**
@@ -210,7 +226,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- mResultDocument.writeToParcel(dest, flags);
+ mResultDocumentWrapper.writeToParcel(dest, flags);
dest.writeBundle(mExtras);
dest.writeInt(mResultCode);
dest.writeString8(mErrorMessage);
@@ -236,24 +252,13 @@
}
/**
- * Returns result codes from throwable.
- *
- * @hide
- */
- static @ResultCode int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
- /**
* The builder for creating {@link ExecuteAppFunctionResponse} instances.
*/
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
public static final class Builder {
@NonNull
- private GenericDocument mResultDocument = new GenericDocument.Builder<>("", "", "").build();
+ private GenericDocument mResultDocument =
+ new GenericDocument.Builder<>("", "", "").build();
@NonNull
private Bundle mExtras = Bundle.EMPTY;
private int mResultCode;
@@ -271,7 +276,8 @@
* with a result code of {@link #RESULT_OK} and a resultDocument.
*/
public Builder(@NonNull GenericDocument resultDocument) {
- mResultDocument = Objects.requireNonNull(resultDocument);
+ Objects.requireNonNull(resultDocument);
+ mResultDocument = resultDocument;
mResultCode = RESULT_OK;
}
@@ -300,7 +306,8 @@
@NonNull
public ExecuteAppFunctionResponse build() {
return new ExecuteAppFunctionResponse(
- mResultDocument, mExtras, mResultCode, mErrorMessage);
+ new GenericDocumentWrapper(mResultDocument),
+ mExtras, mResultCode, mErrorMessage);
}
}
}
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
new file mode 100644
index 0000000..8c76c8e
--- /dev/null
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -0,0 +1,91 @@
+/*
+ * 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.app.appfunctions;
+
+import android.app.appsearch.GenericDocument;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * The Parcelable object contains a {@link GenericDocument} to allow the parcelization of it
+ * exceeding the binder limit.
+ *
+ * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
+ * directly or Android shared memory if the data is large.
+ *
+ * @hide
+ * @see Parcel#writeBlob(byte[])
+ */
+public final class GenericDocumentWrapper implements Parcelable {
+ public static final Creator<GenericDocumentWrapper> CREATOR =
+ new Creator<>() {
+ @Override
+ public GenericDocumentWrapper createFromParcel(Parcel in) {
+ byte[] dataBlob = Objects.requireNonNull(in.readBlob());
+ Parcel unmarshallParcel = Parcel.obtain();
+ try {
+ unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
+ unmarshallParcel.setDataPosition(0);
+ return new GenericDocumentWrapper(
+ GenericDocument.createFromParcel(unmarshallParcel));
+ } finally {
+ unmarshallParcel.recycle();
+ }
+ }
+
+ @Override
+ public GenericDocumentWrapper[] newArray(int size) {
+ return new GenericDocumentWrapper[size];
+ }
+ };
+ @NonNull
+ private final GenericDocument mGenericDocument;
+
+ public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) {
+ mGenericDocument = Objects.requireNonNull(genericDocument);
+ }
+
+ /**
+ * Returns the wrapped {@link android.app.appsearch.GenericDocument}
+ */
+ @NonNull
+ public GenericDocument getValue() {
+ return mGenericDocument;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ mGenericDocument.writeToParcel(parcel, flags);
+ byte[] bytes = parcel.marshall();
+ dest.writeBlob(bytes);
+ } finally {
+ parcel.recycle();
+ }
+
+ }
+}
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 8bd6c85..c38ee08 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -3,6 +3,7 @@
brambonne@google.com
eranm@google.com
jeffv@google.com
+tweek@google.com
per-file *NetworkSecurityPolicy.java = file:net/OWNERS
per-file Confirmation*.java = file:/keystore/OWNERS
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 1535145..815fd1c 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -556,12 +556,9 @@
+ "is a different type from the others. All frames should be the "
+ "same type.");
}
- if (drawableFrame.getIntrinsicWidth() != width ||
- drawableFrame.getIntrinsicHeight() != height) {
- throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
- + "is different. All frames should have the exact same size and "
- + "share the same hotspot.");
- }
+ // TODO(b/361232935): Check when bitmap size of the ith frame is different
+ // drawableFrame.getIntrinsicWidth() != width ||
+ // drawableFrame.getIntrinsicHeight() != height
if (isVectorAnimation) {
drawableFrame = getBitmapDrawableFromVectorDrawable(resources,
(VectorDrawable) drawableFrame, pointerScale);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
new file mode 100644
index 0000000..a489c4f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger
+
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+
+/** Providers for various WmShell-specific coroutines-related constructs. */
+@Module
+class WMShellCoroutinesModule {
+ @Provides
+ @ShellMainThread
+ fun provideMainDispatcher(@ShellMainThread mainExecutor: ShellExecutor): CoroutineDispatcher =
+ mainExecutor.asCoroutineDispatcher()
+
+ @Provides
+ @ShellBackgroundThread
+ fun provideBackgroundDispatcher(
+ @ShellBackgroundThread backgroundExecutor: ShellExecutor
+ ): CoroutineDispatcher = backgroundExecutor.asCoroutineDispatcher()
+
+ @Provides
+ @WMSingleton
+ @ShellMainThread
+ fun provideApplicationScope(
+ @ShellMainThread applicationDispatcher: CoroutineDispatcher,
+ ): CoroutineScope = CoroutineScope(applicationDispatcher)
+
+ @Provides
+ @WMSingleton
+ @ShellBackgroundThread
+ fun provideBackgroundCoroutineScope(
+ @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher,
+ ): CoroutineScope = CoroutineScope(backgroundDispatcher)
+
+ @Provides
+ @WMSingleton
+ @ShellBackgroundThread
+ fun provideBackgroundCoroutineContext(
+ @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher
+ ): CoroutineContext = backgroundDispatcher + SupervisorJob()
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
index 06775e1..685a3ba 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
@@ -23,6 +23,7 @@
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import org.junit.After
@@ -36,13 +37,17 @@
@Postsubmit
open class SnapResizeAppWindowWithButton
@JvmOverloads
-constructor(private val toLeft: Boolean = true) {
+constructor(private val toLeft: Boolean = true, private val isResizable: Boolean = true) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
- private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val testApp = if (isResizable) {
+ DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ } else {
+ DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ }
@Before
fun setup() {
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
index 871602f..8a4aa63 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
@@ -23,6 +23,7 @@
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import org.junit.After
@@ -36,13 +37,17 @@
@Postsubmit
open class SnapResizeAppWindowWithDrag
@JvmOverloads
-constructor(private val toLeft: Boolean = true) {
+constructor(private val toLeft: Boolean = true, private val isResizable: Boolean = true) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
- private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val testApp = if (isResizable) {
+ DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ } else {
+ DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ }
@Before
fun setup() {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2e98c1f..f98b29a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -484,6 +484,7 @@
<activity android:name=".touchpad.tutorial.ui.view.TouchpadTutorialActivity"
android:exported="true"
+ android:showForAllUsers="true"
android:screenOrientation="userLandscape"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
@@ -494,6 +495,7 @@
<activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
android:exported="true"
+ android:showForAllUsers="true"
android:screenOrientation="userLandscape"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index f399436..0555346 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -103,10 +103,10 @@
when {
isSplitShade -> UnsquishingQS(squishiness)
fromScene == Scenes.Shade && toScene == Scenes.QuickSettings -> {
- Expanding(progress)
+ Expanding { progress }
}
fromScene == Scenes.QuickSettings && toScene == Scenes.Shade -> {
- Collapsing(progress)
+ Collapsing { progress }
}
fromScene == Scenes.Shade || toScene == Scenes.Shade -> {
UnsquishingQQS(squishiness)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 94fa9b9..e0a53f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -31,9 +31,8 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,7 +43,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -55,12 +53,7 @@
private val dispatcher = kosmos.testDispatcher
private val zenModeRepository = kosmos.fakeZenModeRepository
- private val underTest =
- ModesTileDataInteractor(
- context,
- ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
- dispatcher
- )
+ private val underTest = ModesTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher)
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index d472d98..22913f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -255,7 +255,7 @@
runCurrent()
clearInvocations(qsImpl!!)
- underTest.setState(QSSceneAdapter.State.Expanding(progress))
+ underTest.setState(QSSceneAdapter.State.Expanding { progress })
with(qsImpl!!) {
verify(this).setQsVisible(true)
verify(this, never())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index 63ce67c..41b5988 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -20,7 +20,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.ui.adapter.ExpandingSubject.Companion.assertThatExpanding
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,33 +37,59 @@
@Test
fun expanding_squishiness1() {
- assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness()).isEqualTo(1f)
+ assertThat(QSSceneAdapter.State.Expanding { 0.3f }.squishiness()).isEqualTo(1f)
}
@Test
fun expandingSpecialValues() {
- assertThat(QSSceneAdapter.State.QQS).isEqualTo(QSSceneAdapter.State.Expanding(0f))
- assertThat(QSSceneAdapter.State.QS).isEqualTo(QSSceneAdapter.State.Expanding(1f))
+ assertThatExpanding(QSSceneAdapter.State.QQS)
+ .isEqualTo(QSSceneAdapter.State.Expanding { 0f })
+ assertThatExpanding(QSSceneAdapter.State.QS)
+ .isEqualTo(QSSceneAdapter.State.Expanding { 1f })
}
@Test
fun collapsing() {
val collapsingProgress = 0.3f
- assertThat(Collapsing(collapsingProgress))
- .isEqualTo(QSSceneAdapter.State.Expanding(1 - collapsingProgress))
+ assertThatExpanding(Collapsing { collapsingProgress })
+ .isEqualTo(QSSceneAdapter.State.Expanding { 1 - collapsingProgress })
}
@Test
fun unsquishingQQS_expansionSameAsQQS() {
val squishiness = 0.6f
- assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion)
- .isEqualTo(QSSceneAdapter.State.QQS.expansion)
+ assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion())
+ .isEqualTo(QSSceneAdapter.State.QQS.expansion())
}
@Test
fun unsquishingQS_expansionSameAsQS() {
val squishiness = 0.6f
- assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion)
- .isEqualTo(QSSceneAdapter.State.QS.expansion)
+ assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion())
+ .isEqualTo(QSSceneAdapter.State.QS.expansion())
+ }
+}
+
+private class ExpandingSubject(
+ metadata: FailureMetadata,
+ private val actual: QSSceneAdapter.State.Expanding?
+) : Subject(metadata, actual) {
+ fun isEqualTo(expected: QSSceneAdapter.State.Expanding) {
+ isNotNull()
+ check("expansion()")
+ .that(actual?.expansion?.invoke())
+ .isEqualTo(expected.expansion.invoke())
+ }
+
+ companion object {
+ fun expanding(): Factory<ExpandingSubject, QSSceneAdapter.State.Expanding> {
+ return Factory { metadata: FailureMetadata, actual: QSSceneAdapter.State.Expanding? ->
+ ExpandingSubject(metadata, actual)
+ }
+ }
+
+ fun assertThatExpanding(actual: QSSceneAdapter.State.Expanding): ExpandingSubject {
+ return assertAbout(expanding()).that(actual)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 11504aa..20d3a7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy.domain.interactor
+import android.app.AutomaticZenRule
import android.app.NotificationManager.Policy
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
@@ -217,4 +218,35 @@
assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id))
.isEqualTo(Duration.ofMinutes(60))
}
+
+ @Test
+ fun mainActiveMode_returnsMainActiveMode() =
+ testScope.runTest {
+ val mainActiveMode by collectLastValue(underTest.mainActiveMode)
+
+ zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+ zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+ runCurrent()
+ assertThat(mainActiveMode).isNull()
+
+ zenModeRepository.activateMode("Other")
+ runCurrent()
+ assertThat(mainActiveMode).isNotNull()
+ assertThat(mainActiveMode!!.id).isEqualTo("Other")
+
+ zenModeRepository.activateMode("Bedtime")
+ runCurrent()
+ assertThat(mainActiveMode).isNotNull()
+ assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+
+ zenModeRepository.deactivateMode("Other")
+ runCurrent()
+ assertThat(mainActiveMode).isNotNull()
+ assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+
+ zenModeRepository.deactivateMode("Bedtime")
+ runCurrent()
+ assertThat(mainActiveMode).isNull()
+ }
}
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 5d81d4f..9f9c8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -53,6 +53,10 @@
import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
@@ -65,6 +69,7 @@
import com.android.systemui.util.LifecycleFragment
import java.util.function.Consumer
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@@ -77,6 +82,8 @@
@Inject
constructor(
private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+ @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
+ @Named(QS_PANEL) private val qsMediaHost: MediaHost,
) : LifecycleFragment(), QS {
private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
@@ -99,6 +106,8 @@
QSComposeFragment.isUnexpectedlyInLegacyMode()
viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
+ qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+ qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
setListenerCollections()
}
@@ -274,7 +283,7 @@
) {}
override fun isFullyCollapsed(): Boolean {
- return !viewModel.isQSVisible
+ return viewModel.qsExpansionValue <= 0f
}
override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
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 9e109e4..7d52216 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
@@ -18,6 +18,7 @@
import android.content.res.Resources
import android.graphics.Rect
+import androidx.annotation.FloatRange
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.qualifiers.Main
@@ -79,11 +80,17 @@
_qsVisible.value = value
}
- private val _qsExpansion = MutableStateFlow(0f)
+ // This can only be negative if undefined (in which case it will be -1f), else it will be
+ // in [0, 1]. In some cases, it could be set back to -1f internally to indicate that it's
+ // different to every value in [0, 1].
+ @FloatRange(from = -1.0, to = 1.0) private val _qsExpansion = MutableStateFlow(-1f)
var qsExpansionValue: Float
get() = _qsExpansion.value
set(value) {
- _qsExpansion.value = value
+ if (value < 0f) {
+ _qsExpansion.value = -1f
+ }
+ _qsExpansion.value = value.coerceIn(0f, 1f)
}
private val _panelFraction = MutableStateFlow(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index dfcf216..ac6ebe7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -147,17 +147,17 @@
sealed interface State {
val isVisible: Boolean
- val expansion: Float
+ val expansion: () -> Float
val squishiness: () -> Float
data object CLOSED : State {
override val isVisible = false
- override val expansion = 0f
+ override val expansion = { 0f }
override val squishiness = { 1f }
}
/** State for expanding between QQS and QS */
- data class Expanding(override val expansion: Float) : State {
+ class Expanding(override val expansion: () -> Float) : State {
override val isVisible = true
override val squishiness = { 1f }
}
@@ -170,7 +170,7 @@
*/
class UnsquishingQQS(override val squishiness: () -> Float) : State {
override val isVisible = true
- override val expansion = 0f
+ override val expansion = { 0f }
}
/**
@@ -181,16 +181,16 @@
*/
class UnsquishingQS(override val squishiness: () -> Float) : State {
override val isVisible = true
- override val expansion = 1f
+ override val expansion = { 1f }
}
companion object {
// These are special cases of the expansion.
- val QQS = Expanding(0f)
- val QS = Expanding(1f)
+ val QQS = Expanding { 0f }
+ val QS = Expanding { 1f }
/** Collapsing from QS to QQS. [progress] is 0f in QS and 1f in QQS. */
- fun Collapsing(progress: Float) = Expanding(1f - progress)
+ fun Collapsing(progress: () -> Float) = Expanding { 1f - progress() }
}
}
}
@@ -418,14 +418,14 @@
private fun QSImpl.applyState(state: QSSceneAdapter.State) {
setQsVisible(state.isVisible)
- setExpanded(state.isVisible && state.expansion > 0f)
+ setExpanded(state.isVisible && state.expansion() > 0f)
setListening(state.isVisible)
}
override fun applyLatestExpansionAndSquishiness() {
val qsImpl = _qsImpl.value
val state = state.value
- qsImpl?.setQsExpansion(state.expansion, 1f, 0f, state.squishiness())
+ qsImpl?.setQsExpansion(state.expansion(), 1f, 0f, state.squishiness())
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
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 7586133..f16fcb5 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
@@ -42,6 +42,7 @@
class ZenModeInteractor
@Inject
constructor(
+ private val context: Context,
private val zenModeRepository: ZenModeRepository,
private val notificationSettingsRepository: NotificationSettingsRepository,
) {
@@ -78,6 +79,26 @@
val activeModes: Flow<List<ZenMode>> =
modes.map { modes -> modes.filter { mode -> mode.isActive } }.distinctUntilChanged()
+ /** Flow returning the most prioritized of the active modes, if any. */
+ val mainActiveMode: Flow<ZenMode?> =
+ activeModes.map { modes -> getMainActiveMode(modes) }.distinctUntilChanged()
+
+ /**
+ * Given the list of modes (which may include zero or more currently active modes), returns the
+ * most prioritized of the active modes, if any.
+ */
+ private fun getMainActiveMode(modes: List<ZenMode>): ZenMode? {
+ return modes.sortedWith(ZenMode.PRIORITIZING_COMPARATOR).firstOrNull { it.isActive }
+ }
+
+ suspend fun getModeIcon(mode: ZenMode): Icon {
+ 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
@@ -86,16 +107,7 @@
* package).
*/
suspend fun getActiveModeIcon(context: Context, modes: List<ZenMode>): Icon? {
- return modes
- .sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
- .firstOrNull { it.isActive }
- ?.getLockscreenIcon(context, iconLoader)
- ?.await()
- ?.asIcon()
- }
-
- suspend fun getModeIcon(context: Context, mode: ZenMode): Icon {
- return mode.getIcon(context, iconLoader).await().asIcon()
+ return getMainActiveMode(modes)?.let { m -> getLockscreenModeIcon(m) }
}
fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 02b5e49..be90bec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -88,7 +88,7 @@
modesList.map { mode ->
ModeTileViewModel(
id = mode.id,
- icon = zenModeInteractor.getModeIcon(context, mode),
+ icon = zenModeInteractor.getModeIcon(mode),
text = mode.name,
subtext = getTileSubtext(mode),
enabled = mode.isActive,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index c2f035f1..19735e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -93,7 +93,7 @@
private val tileDataInteractor =
ModesTileDataInteractor(
context,
- ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
+ ZenModeInteractor(context, zenModeRepository, mock<NotificationSettingsRepository>()),
testDispatcher
)
private val mapper =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index aef0828..66be7e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy.domain.interactor
+import android.content.testableContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
@@ -23,6 +24,7 @@
val Kosmos.zenModeInteractor by Fixture {
ZenModeInteractor(
+ context = testableContext,
zenModeRepository = zenModeRepository,
notificationSettingsRepository = notificationSettingsRepository,
)