Merge "Rename the methods to easier understand what they do" into tm-qpr-dev
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index 794c694..c5dedb3 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -173,7 +173,7 @@
@TestApi
public void setSystemLocales(@NonNull LocaleList locales) {
try {
- Configuration conf = ActivityManager.getService().getConfiguration();
+ Configuration conf = new Configuration();
conf.setLocales(locales);
ActivityManager.getService().updatePersistentConfiguration(conf);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index f2525d1..ade9fd6 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -160,6 +160,7 @@
* Disables list updates and releases all resources.
*/
public void close() {
+ OnCloseListener onCompleteListenersCopied = null;
synchronized (mLock) {
if (mIsClosed) return;
mIsClosed = true;
@@ -167,10 +168,14 @@
mListCallbacks.clear();
mOnCompleteListeners.clear();
if (mOnCloseListener != null) {
- mOnCloseListener.onClose();
+ onCompleteListenersCopied = mOnCloseListener;
mOnCloseListener = null;
}
}
+
+ if (onCompleteListenersCopied != null) {
+ onCompleteListenersCopied.onClose();
+ }
}
void apply(Chunk chunk) {
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 0aafaf4..6091bf9 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -50,10 +50,10 @@
private final Context mContext;
@GuardedBy("mBrokenListeners")
- private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
+ private final ArrayList<MultiVibratorStateListener> mBrokenListeners = new ArrayList<>();
@GuardedBy("mRegisteredListeners")
- private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
+ private final ArrayMap<OnVibratorStateChangedListener, MultiVibratorStateListener>
mRegisteredListeners = new ArrayMap<>();
private final Object mLock = new Object();
@@ -147,7 +147,7 @@
Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
return;
}
- AllVibratorsStateListener delegate = null;
+ MultiVibratorStateListener delegate = null;
try {
synchronized (mRegisteredListeners) {
// If listener is already registered, reject and return.
@@ -155,7 +155,7 @@
Log.w(TAG, "Listener already registered.");
return;
}
- delegate = new AllVibratorsStateListener(executor, listener);
+ delegate = new MultiVibratorStateListener(executor, listener);
delegate.register(mVibratorManager);
mRegisteredListeners.put(listener, delegate);
delegate = null;
@@ -181,7 +181,7 @@
}
synchronized (mRegisteredListeners) {
if (mRegisteredListeners.containsKey(listener)) {
- AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
+ MultiVibratorStateListener delegate = mRegisteredListeners.get(listener);
delegate.unregister(mVibratorManager);
mRegisteredListeners.remove(listener);
}
@@ -238,7 +238,7 @@
* Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
* that were left registered to vibrators after failures to register them to all vibrators.
*
- * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
+ * <p>This might happen if {@link MultiVibratorStateListener} fails to register to any vibrator
* and also fails to unregister any previously registered single listeners to other vibrators.
*
* <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
@@ -259,10 +259,10 @@
/** Listener for a single vibrator state change. */
private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
- private final AllVibratorsStateListener mAllVibratorsListener;
+ private final MultiVibratorStateListener mAllVibratorsListener;
private final int mVibratorIdx;
- SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
+ SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx) {
mAllVibratorsListener = listener;
mVibratorIdx = vibratorIdx;
}
@@ -552,8 +552,16 @@
}
}
- /** Listener for all vibrators state change. */
- private static class AllVibratorsStateListener {
+ /**
+ * Listener for all vibrators state change.
+ *
+ * <p>This registers a listener to all vibrators to merge the callbacks into a single state
+ * that is set to true if any individual vibrator is also true, and false otherwise.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class MultiVibratorStateListener {
private final Object mLock = new Object();
private final Executor mExecutor;
private final OnVibratorStateChangedListener mDelegate;
@@ -567,19 +575,21 @@
@GuardedBy("mLock")
private int mVibratingMask;
- AllVibratorsStateListener(@NonNull Executor executor,
+ public MultiVibratorStateListener(@NonNull Executor executor,
@NonNull OnVibratorStateChangedListener listener) {
mExecutor = executor;
mDelegate = listener;
}
- boolean hasRegisteredListeners() {
+ /** Returns true if at least one listener was registered to an individual vibrator. */
+ public boolean hasRegisteredListeners() {
synchronized (mLock) {
return mVibratorListeners.size() > 0;
}
}
- void register(VibratorManager vibratorManager) {
+ /** Registers a listener to all individual vibrators in {@link VibratorManager}. */
+ public void register(VibratorManager vibratorManager) {
int[] vibratorIds = vibratorManager.getVibratorIds();
synchronized (mLock) {
for (int i = 0; i < vibratorIds.length; i++) {
@@ -603,7 +613,8 @@
}
}
- void unregister(VibratorManager vibratorManager) {
+ /** Unregisters the listeners from all individual vibrators in {@link VibratorManager}. */
+ public void unregister(VibratorManager vibratorManager) {
synchronized (mLock) {
for (int i = mVibratorListeners.size(); --i >= 0; ) {
int vibratorId = mVibratorListeners.keyAt(i);
@@ -614,30 +625,44 @@
}
}
- void onVibrating(int vibratorIdx, boolean vibrating) {
+ /** Callback triggered by {@link SingleVibratorStateListener} for each vibrator. */
+ public void onVibrating(int vibratorIdx, boolean vibrating) {
mExecutor.execute(() -> {
- boolean anyVibrating;
+ boolean shouldNotifyStateChange;
+ boolean isAnyVibrating;
synchronized (mLock) {
+ // Bitmask indicating that all vibrators have been initialized.
int allInitializedMask = (1 << mVibratorListeners.size()) - 1;
- int vibratorMask = 1 << vibratorIdx;
- if ((mInitializedMask & vibratorMask) == 0) {
- // First state report for this vibrator, set vibrating initial value.
- mInitializedMask |= vibratorMask;
- mVibratingMask |= vibrating ? vibratorMask : 0;
- } else {
- // Flip vibrating value, if changed.
- boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
- if (prevVibrating != vibrating) {
- mVibratingMask ^= vibratorMask;
- }
+
+ // Save current global state before processing this vibrator state change.
+ boolean previousIsAnyVibrating = (mVibratingMask != 0);
+ boolean previousAreAllInitialized = (mInitializedMask == allInitializedMask);
+
+ // Mark this vibrator as initialized.
+ int vibratorMask = (1 << vibratorIdx);
+ mInitializedMask |= vibratorMask;
+
+ // Flip the vibrating bit flag for this vibrator, only if the state is changing.
+ boolean previousVibrating = (mVibratingMask & vibratorMask) != 0;
+ if (previousVibrating != vibrating) {
+ mVibratingMask ^= vibratorMask;
}
- if (mInitializedMask != allInitializedMask) {
- // Wait for all vibrators initial state to be reported before delegating.
- return;
- }
- anyVibrating = mVibratingMask != 0;
+
+ // Check new global state after processing this vibrator state change.
+ isAnyVibrating = (mVibratingMask != 0);
+ boolean areAllInitialized = (mInitializedMask == allInitializedMask);
+
+ // Prevent multiple triggers with the same state.
+ // Trigger once when all vibrators have reported their state, and then only when
+ // the merged vibrating state changes.
+ boolean isStateChanging = (previousIsAnyVibrating != isAnyVibrating);
+ shouldNotifyStateChange =
+ areAllInitialized && (!previousAreAllInitialized || isStateChanging);
}
- mDelegate.onVibratorStateChanged(anyVibrating);
+ // Notify delegate listener outside the lock, only if merged state is changing.
+ if (shouldNotifyStateChange) {
+ mDelegate.onVibratorStateChanged(isAnyVibrating);
+ }
});
}
}
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 3c53d07..7dd1d26 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -311,8 +311,7 @@
try {
final IActivityManager am = ActivityManager.getService();
- final Configuration config = am.getConfiguration();
-
+ final Configuration config = new Configuration();
config.setLocales(locales);
config.userSetLocale = true;
diff --git a/core/res/res/color/letterbox_background.xml b/core/res/res/color/letterbox_background.xml
new file mode 100644
index 0000000..955948a
--- /dev/null
+++ b/core/res/res/color/letterbox_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/system_neutral1_500" android:lStar="5" />
+</selector>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 881d499..aa9a949 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5239,7 +5239,7 @@
but isn't supported on the device or both dark scrim alpha and blur radius aren't
provided.
-->
- <color name="config_letterboxBackgroundColor">@android:color/system_neutral2_900</color>
+ <color name="config_letterboxBackgroundColor">@color/letterbox_background</color>
<!-- Horizontal position of a center of the letterboxed app window.
0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 7a66bef..7ebebc9 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -21,12 +21,17 @@
import static junit.framework.TestCase.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
@@ -34,6 +39,7 @@
import android.content.ContextWrapper;
import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
+import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -46,6 +52,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.junit.MockitoJUnitRunner;
/**
@@ -65,6 +72,7 @@
private Context mContextSpy;
private Vibrator mVibratorSpy;
+ private TestLooper mTestLooper;
@Before
public void setUp() {
@@ -73,6 +81,7 @@
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
mVibratorSpy = spy(new SystemVibrator(mContextSpy));
+ mTestLooper = new TestLooper();
}
@Test
@@ -395,6 +404,108 @@
}
@Test
+ public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]);
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+
+ // Never tries to register a listener to an individual vibrator.
+ assertFalse(multiVibratorListener.hasRegisteredListeners());
+ verify(mockVibratorManager, never()).getVibrator(anyInt());
+ }
+
+ @Test
+ public void onVibratorStateChanged_singleVibrator_forwardsAllCallbacks() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1 });
+ when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+ assertTrue(multiVibratorListener.hasRegisteredListeners());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true);
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
+
+ mTestLooper.dispatchAll();
+
+ InOrder inOrder = inOrder(mockListener);
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(true));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void onVibratorStateChanged_multipleVibrators_triggersOnlyWhenAllVibratorsInitialized() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+ when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+ assertTrue(multiVibratorListener.hasRegisteredListeners());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
+ mTestLooper.dispatchAll();
+ verify(mockListener, never()).onVibratorStateChanged(anyBoolean());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false);
+ mTestLooper.dispatchAll();
+ verify(mockListener).onVibratorStateChanged(eq(false));
+ verifyNoMoreInteractions(mockListener);
+ }
+
+ @Test
+ public void onVibratorStateChanged_multipleVibrators_stateChangeIsDeduped() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+ when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+ assertTrue(multiVibratorListener.hasRegisteredListeners());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // none
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true); // true
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ true); // true
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // true
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false
+
+ mTestLooper.dispatchAll();
+
+ InOrder inOrder = inOrder(mockListener);
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(true));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
public void vibrate_withVibrationAttributes_usesGivenAttributes() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 00be5a6e..77284c41 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -109,6 +109,12 @@
return (mSplitRule instanceof SplitPlaceholderRule);
}
+ @NonNull
+ SplitInfo toSplitInfo() {
+ return new SplitInfo(mPrimaryContainer.toActivityStack(),
+ mSecondaryContainer.toActivityStack(), mSplitAttributes);
+ }
+
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index bf7326a..1d513e4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1422,6 +1422,11 @@
@GuardedBy("mLock")
void updateContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
+ if (!container.getTaskContainer().isVisible()) {
+ // Wait until the Task is visible to avoid unnecessary update when the Task is still in
+ // background.
+ return;
+ }
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1643,16 +1648,14 @@
/**
* Notifies listeners about changes to split states if necessary.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private void updateCallbackIfNecessary() {
- if (mEmbeddingCallback == null) {
+ void updateCallbackIfNecessary() {
+ if (mEmbeddingCallback == null || !readyToReportToClient()) {
return;
}
- if (!allActivitiesCreated()) {
- return;
- }
- List<SplitInfo> currentSplitStates = getActiveSplitStates();
- if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
+ final List<SplitInfo> currentSplitStates = getActiveSplitStates();
+ if (mLastReportedSplitStates.equals(currentSplitStates)) {
return;
}
mLastReportedSplitStates.clear();
@@ -1661,48 +1664,27 @@
}
/**
- * @return a list of descriptors for currently active split states. If the value returned is
- * null, that indicates that the active split states are in an intermediate state and should
- * not be reported.
+ * Returns a list of descriptors for currently active split states.
*/
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private List<SplitInfo> getActiveSplitStates() {
- List<SplitInfo> splitStates = new ArrayList<>();
+ final List<SplitInfo> splitStates = new ArrayList<>();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
- .mSplitContainers;
- for (SplitContainer container : splitContainers) {
- if (container.getPrimaryContainer().isEmpty()
- || container.getSecondaryContainer().isEmpty()) {
- // We are in an intermediate state because either the split container is about
- // to be removed or the primary or secondary container are about to receive an
- // activity.
- return null;
- }
- final ActivityStack primaryContainer = container.getPrimaryContainer()
- .toActivityStack();
- final ActivityStack secondaryContainer = container.getSecondaryContainer()
- .toActivityStack();
- final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
- container.getSplitAttributes());
- splitStates.add(splitState);
- }
+ mTaskContainers.valueAt(i).getSplitStates(splitStates);
}
return splitStates;
}
/**
- * Checks if all activities that are registered with the containers have already appeared in
- * the client.
+ * Whether we can now report the split states to the client.
*/
- private boolean allActivitiesCreated() {
+ @GuardedBy("mLock")
+ private boolean readyToReportToClient() {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
- for (TaskFragmentContainer container : containers) {
- if (!container.taskInfoActivityCountMatchesCreated()) {
- return false;
- }
+ if (mTaskContainers.valueAt(i).isInIntermediateState()) {
+ // If any Task is in an intermediate state, wait for the server update.
+ return false;
}
}
return true;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 00943f2d..231da05 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -221,6 +221,24 @@
return mContainers.indexOf(child);
}
+ /** Whether the Task is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.isInIntermediateState()) {
+ // We are in an intermediate state to wait for server update on this TaskFragment.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
+ void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
+ for (SplitContainer container : mSplitContainers) {
+ outSplitStates.add(container.toSplitInfo());
+ }
+ }
+
/**
* A wrapper class which contains the display ID and {@link Configuration} of a
* {@link TaskContainer}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index ef5ea56..a7d47ef 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -161,7 +161,7 @@
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 18712ae..71b8840 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -166,16 +166,34 @@
return allActivities;
}
- /**
- * Checks if the count of activities from the same process in task fragment info corresponds to
- * the ones created and available on the client side.
- */
- boolean taskInfoActivityCountMatchesCreated() {
+ /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
if (mInfo == null) {
- return false;
+ // Haven't received onTaskFragmentAppeared event.
+ return true;
}
- return mPendingAppearedActivities.isEmpty()
- && mInfo.getActivities().size() == collectNonFinishingActivities().size();
+ if (mInfo.isEmpty()) {
+ // Empty TaskFragment will be removed or will have activity launched into it soon.
+ return true;
+ }
+ if (!mPendingAppearedActivities.isEmpty()) {
+ // Reparented activity hasn't appeared.
+ return true;
+ }
+ // Check if there is any reported activity that is no longer alive.
+ for (IBinder token : mInfo.getActivities()) {
+ final Activity activity = mController.getActivity(token);
+ if (activity == null && !mTaskContainer.isVisible()) {
+ // Activity can be null if the activity is not attached to process yet. That can
+ // happen when the activity is started in background.
+ continue;
+ }
+ if (activity == null || activity.isFinishing()) {
+ // One of the reported activity is no longer alive, wait for the server update.
+ return true;
+ }
+ }
+ return false;
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index a403031..87d0278 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -102,6 +102,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/**
* Test class for {@link SplitController}.
@@ -132,6 +133,8 @@
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
+ private Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private List<SplitInfo> mSplitInfos;
private TransactionManager mTransactionManager;
@Before
@@ -141,9 +144,16 @@
.getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter;
+ mSplitInfos = new ArrayList<>();
+ mEmbeddingCallback = splitInfos -> {
+ mSplitInfos.clear();
+ mSplitInfos.addAll(splitInfos);
+ };
+ mSplitController.setSplitInfoCallback(mEmbeddingCallback);
mTransactionManager = mSplitController.mTransactionManager;
spyOn(mSplitController);
spyOn(mSplitPresenter);
+ spyOn(mEmbeddingCallback);
spyOn(mTransactionManager);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
@@ -329,6 +339,30 @@
}
@Test
+ public void testUpdateContainer_skipIfTaskIsInvisible() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+ spyOn(taskContainer);
+
+ // No update when the Task is invisible.
+ clearInvocations(mSplitPresenter);
+ doReturn(false).when(taskContainer).isVisible();
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
+
+ // Update the split when the Task is visible.
+ doReturn(true).when(taskContainer).isVisible();
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+
+ verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+ taskFragmentContainer, mTransaction);
+ }
+
+ @Test
public void testOnStartActivityResultError() {
final Intent intent = new Intent();
final TaskContainer taskContainer = createTestTaskContainer();
@@ -1162,14 +1196,69 @@
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
}
+ @Test
+ public void testSplitInfoCallback_reportSplit() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(1, mSplitInfos.size());
+ final SplitInfo splitInfo = mSplitInfos.get(0);
+ assertEquals(1, splitInfo.getPrimaryActivityStack().getActivities().size());
+ assertEquals(1, splitInfo.getSecondaryActivityStack().getActivities().size());
+ assertEquals(r0, splitInfo.getPrimaryActivityStack().getActivities().get(0));
+ assertEquals(r1, splitInfo.getSecondaryActivityStack().getActivities().get(0));
+ }
+
+ @Test
+ public void testSplitInfoCallback_reportSplitInMultipleTasks() {
+ final int taskId0 = 1;
+ final int taskId1 = 2;
+ final Activity r0 = createMockActivity(taskId0);
+ final Activity r1 = createMockActivity(taskId0);
+ final Activity r2 = createMockActivity(taskId1);
+ final Activity r3 = createMockActivity(taskId1);
+ addSplitTaskFragments(r0, r1);
+ addSplitTaskFragments(r2, r3);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(2, mSplitInfos.size());
+ }
+
+ @Test
+ public void testSplitInfoCallback_doNotReportIfInIntermediateState() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskFragmentContainer tf0 = mSplitController.getContainerWithActivity(r0);
+ final TaskFragmentContainer tf1 = mSplitController.getContainerWithActivity(r1);
+ spyOn(tf0);
+ spyOn(tf1);
+
+ // Do not report if activity has not appeared in the TaskFragmentContainer in split.
+ doReturn(true).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback, never()).accept(any());
+
+ doReturn(false).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback).accept(any());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
+ return createMockActivity(TASK_ID);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ private Activity createMockActivity(int taskId) {
final Activity activity = mock(Activity.class);
doReturn(mActivityResources).when(activity).getResources();
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
- doReturn(TASK_ID).when(activity).getTaskId();
+ doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
return activity;
@@ -1177,7 +1266,8 @@
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
- final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
setupTaskFragmentInfo(container, activity);
return container;
}
@@ -1268,7 +1358,7 @@
// We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop.
- final int windowingMode = mSplitController.getTaskContainer(TASK_ID)
+ final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
.getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
primaryContainer.setLastRequestedWindowingMode(windowingMode);
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 35415d8..d43c471 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -334,6 +334,70 @@
assertFalse(container.hasActivity(mActivity.getActivityToken()));
}
+ @Test
+ public void testIsInIntermediateState() {
+ // True if no info set.
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController);
+ spyOn(taskContainer);
+ doReturn(true).when(taskContainer).isVisible();
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if empty info set.
+ final List<IBinder> activities = new ArrayList<>();
+ doReturn(activities).when(mInfo).getActivities();
+ doReturn(true).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if info is not empty.
+ doReturn(false).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is pending appeared activity.
+ container.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if the activity is finishing.
+ activities.add(mActivity.getActivityToken());
+ doReturn(true).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if the activity is not finishing.
+ doReturn(false).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is a token that can't find associated activity.
+ activities.clear();
+ activities.add(new Binder());
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if there is a token that can't find associated activity when the Task is invisible.
+ doReturn(false).when(taskContainer).isVisible();
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 30c3d50..df5f921 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -23,6 +23,10 @@
TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
<bool name="config_registerShellTaskOrganizerOnInit">true</bool>
+ <!-- Determines whether to register the shell transitions on init.
+ TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
+ <bool name="config_registerShellTransitionsOnInit">true</bool>
+
<!-- Animation duration for PIP when entering. -->
<integer name="config_pipEnterAnimationDuration">425</integer>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index d76ad3d..477bc95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -41,7 +41,6 @@
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -123,7 +122,7 @@
/** Until all users are converted, we may have mixed-use (eg. Car). */
private boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
+ return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
index 83335ac..07d5012 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -87,6 +87,10 @@
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
+ boolean isEnabled() {
+ return mTransitions.isRegistered();
+ }
+
/**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 490975c..921861a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -303,6 +303,7 @@
// 3. Animate the TaskFragment using Activity Change info (start/end bounds).
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
+ Animation changeAnimation = null;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -325,8 +326,14 @@
}
}
+ // There are two animations in the array. The first one is for the start leash
+ // (snapshot), and the second one is for the end leash (TaskFragment).
final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
boundsAnimationChange.getEndAbsBounds());
+ // Keep track as we might need to add background color for the animation.
+ // Although there may be multiple change animation, record one of them is sufficient
+ // because the background color will be added to the root leash for the whole animation.
+ changeAnimation = animations[1];
// Create a screenshot based on change, but attach it to the top of the
// boundsAnimationChange.
@@ -345,6 +352,9 @@
animations[1], boundsAnimationChange));
}
+ // If there is no corresponding open/close window with the change, we should show background
+ // color to cover the empty part of the screen.
+ boolean shouldShouldBackgroundColor = true;
// Handle the other windows that don't have bounds change in the same transition.
for (TransitionInfo.Change change : info.getChanges()) {
if (handledChanges.contains(change)) {
@@ -359,11 +369,20 @@
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (Transitions.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+ shouldShouldBackgroundColor = false;
} else {
animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+ shouldShouldBackgroundColor = false;
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
}
+
+ if (shouldShouldBackgroundColor && changeAnimation != null) {
+ // Change animation may leave part of the screen empty. Show background color to cover
+ // that.
+ changeAnimation.setShowBackdrop(true);
+ }
+
return adapters;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 58b2366..2bb7369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -158,7 +158,7 @@
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 28a1959..625d8a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -506,6 +506,10 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
+ if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
+ // TODO(b/238217847): Force override shell init if registration is disabled
+ shellInit = new ShellInit(mainExecutor);
+ }
return new Transitions(context, shellInit, shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 195ff50..2fafe67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -48,7 +48,6 @@
try {
int result = Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
return result != 0;
} catch (Exception e) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c91d54a..b7749fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -69,22 +69,28 @@
/**
* Mark a task with given [taskId] as active.
+ *
+ * @return `true` if the task was not active
*/
- fun addActiveTask(taskId: Int) {
+ fun addActiveTask(taskId: Int): Boolean {
val added = activeTasks.add(taskId)
if (added) {
activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return added
}
/**
* Remove task with given [taskId] from active tasks.
+ *
+ * @return `true` if the task was active
*/
- fun removeActiveTask(taskId: Int) {
+ fun removeActiveTask(taskId: Int): Boolean {
val removed = activeTasks.remove(taskId)
if (removed) {
activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return removed
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index eaa7158..90b35a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -87,11 +87,13 @@
}
if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, true));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
+ });
}
}
@@ -102,11 +104,13 @@
mTasks.remove(taskInfo.taskId);
if (DesktopModeStatus.IS_SUPPORTED) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, false));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (repository.removeActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Removing active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, false);
+ });
}
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -123,13 +127,15 @@
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
if (DesktopModeStatus.IS_SUPPORTED) {
- if (taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- }
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (taskInfo.isVisible) {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible);
+ });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index b9746e3..cbed4b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -115,8 +115,8 @@
// coordinates so offset the bounds to 0,0
mTmpDestinationRect.offsetTo(0, 0);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset source
- // rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
@@ -129,9 +129,8 @@
: (float) destinationBounds.height() / sourceBounds.height();
scale = (1 - fraction) * startScale + fraction * endScale;
} else {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
}
final float left = destinationBounds.left - insets.left * scale;
final float top = destinationBounds.top - insets.top * scale;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index d7ca791..21a1310 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -108,6 +108,14 @@
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+ final TransitSession pendingTransition = getPendingTransition(transition);
+ if (pendingTransition != null && pendingTransition.mCanceled) {
+ // The pending transition was canceled, so skip playing animation.
+ t.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ return;
+ }
+
// Play some place-holder fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -170,9 +178,7 @@
}
boolean isPendingTransition(IBinder transition) {
- return isPendingEnter(transition)
- || isPendingDismiss(transition)
- || isPendingRecent(transition);
+ return getPendingTransition(transition) != null;
}
boolean isPendingEnter(IBinder transition) {
@@ -187,22 +193,38 @@
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
+ @Nullable
+ private TransitSession getPendingTransition(IBinder transition) {
+ if (isPendingEnter(transition)) {
+ return mPendingEnter;
+ } else if (isPendingRecent(transition)) {
+ return mPendingRecent;
+ } else if (isPendingDismiss(transition)) {
+ return mPendingDismiss;
+ }
+
+ return null;
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@WindowManager.TransitionType int transitType,
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- @Nullable TransitionCallback callback) {
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, callback);
+ setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingEnter = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
+ mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -237,8 +259,9 @@
}
void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingRecent = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -248,7 +271,7 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Enter recent panel");
+ + " deduced Enter recent panel");
}
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
@@ -256,14 +279,9 @@
if (mergeTarget != mAnimatingTransition) return;
if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
- mPendingRecent.mCallback = new TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Since there's an entering transition merged, recent transition no longer
- // need to handle entering split screen after the transition finished.
- }
- };
+ // Since there's an entering transition merged, recent transition no longer
+ // need to handle entering split screen after the transition finished.
+ mPendingRecent.setFinishedCallback(null);
}
if (mActiveRemoteHandler != null) {
@@ -277,7 +295,7 @@
}
boolean end() {
- // If its remote, there's nothing we can do right now.
+ // If It's remote, there's nothing we can do right now.
if (mActiveRemoteHandler != null) return false;
for (int i = mAnimations.size() - 1; i >= 0; --i) {
final Animator anim = mAnimations.get(i);
@@ -290,20 +308,20 @@
@Nullable SurfaceControl.Transaction finishT) {
if (isPendingEnter(transition)) {
if (!aborted) {
- // An enter transition got merged, appends the rest operations to finish entering
+ // An entering transition got merged, appends the rest operations to finish entering
// split screen.
mStageCoordinator.finishEnterSplitScreen(finishT);
mPendingRemoteHandler = null;
}
- mPendingEnter.mCallback.onTransitionConsumed(aborted);
+ mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
mPendingRemoteHandler = null;
} else if (isPendingDismiss(transition)) {
- mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+ mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
} else if (isPendingRecent(transition)) {
- mPendingRecent.mCallback.onTransitionConsumed(aborted);
+ mPendingRecent.onConsumed(aborted);
mPendingRecent = null;
mPendingRemoteHandler = null;
}
@@ -312,23 +330,16 @@
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
- TransitionCallback callback = null;
+ if (wct == null) wct = new WindowContainerTransaction();
if (isPendingEnter(mAnimatingTransition)) {
- callback = mPendingEnter.mCallback;
+ mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
- }
- if (isPendingDismiss(mAnimatingTransition)) {
- callback = mPendingDismiss.mCallback;
- mPendingDismiss = null;
- }
- if (isPendingRecent(mAnimatingTransition)) {
- callback = mPendingRecent.mCallback;
+ } else if (isPendingRecent(mAnimatingTransition)) {
+ mPendingRecent.onFinished(wct, mFinishTransaction);
mPendingRecent = null;
- }
-
- if (callback != null) {
- if (wct == null) wct = new WindowContainerTransaction();
- callback.onTransitionFinished(wct, mFinishTransaction);
+ } else if (isPendingDismiss(mAnimatingTransition)) {
+ mPendingDismiss.onFinished(wct, mFinishTransaction);
+ mPendingDismiss = null;
}
mPendingRemoteHandler = null;
@@ -363,10 +374,7 @@
onFinish(null /* wct */, null /* wctCB */);
});
};
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
+ va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
@@ -376,9 +384,6 @@
public void onAnimationCancel(Animator animation) {
finisher.run();
}
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
@@ -432,24 +437,66 @@
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
- /** Clean-up callbacks for transition. */
- interface TransitionCallback {
- /** Calls when the transition got consumed. */
- default void onTransitionConsumed(boolean aborted) {}
+ /** Calls when the transition got consumed. */
+ interface TransitionConsumedCallback {
+ void onConsumed(boolean aborted);
+ }
- /** Calls when the transition finished. */
- default void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {}
+ /** Calls when the transition finished. */
+ interface TransitionFinishedCallback {
+ void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
}
/** Session for a transition and its clean-up callback. */
static class TransitSession {
final IBinder mTransition;
- TransitionCallback mCallback;
+ TransitionConsumedCallback mConsumedCallback;
+ TransitionFinishedCallback mFinishedCallback;
- TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+ /** Whether the transition was canceled. */
+ boolean mCanceled;
+
+ TransitSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
mTransition = transition;
- mCallback = callback != null ? callback : new TransitionCallback() {};
+ mConsumedCallback = consumedCallback;
+ mFinishedCallback = finishedCallback;
+
+ }
+
+ /** Sets transition consumed callback. */
+ void setConsumedCallback(@Nullable TransitionConsumedCallback callback) {
+ mConsumedCallback = callback;
+ }
+
+ /** Sets transition finished callback. */
+ void setFinishedCallback(@Nullable TransitionFinishedCallback callback) {
+ mFinishedCallback = callback;
+ }
+
+ /**
+ * Cancels the transition. This should be called before playing animation. A canceled
+ * transition will skip playing animation.
+ *
+ * @param finishedCb new finish callback to override.
+ */
+ void cancel(@Nullable TransitionFinishedCallback finishedCb) {
+ mCanceled = true;
+ setFinishedCallback(finishedCb);
+ }
+
+ void onConsumed(boolean aborted) {
+ if (mConsumedCallback != null) {
+ mConsumedCallback.onConsumed(aborted);
+ }
+ }
+
+ void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (mFinishedCallback != null) {
+ mFinishedCallback.onFinished(finishWct, finishT);
+ }
}
}
@@ -459,7 +506,7 @@
final @SplitScreen.StageType int mDismissTop;
DismissTransition(IBinder transition, int reason, int dismissTop) {
- super(transition, null /* callback */);
+ super(transition, null /* consumedCallback */, null /* finishedCallback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
}
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 e2ac01f..943419b 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
@@ -226,33 +226,36 @@
}
};
- private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current split, so we
- // can restore the divider bar.
- for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
+ private final SplitScreenTransitions.TransitionFinishedCallback
+ mRecentTransitionFinishedCallback =
+ new SplitScreenTransitions.TransitionFinishedCallback() {
+ @Override
+ public void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current
+ // split, so we
+ // can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
+ }
+ };
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
@@ -389,15 +392,11 @@
if (ENABLE_SHELL_TRANSITIONS) {
prepareEnterSplitScreen(wct);
mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ null, this, null /* consumedCallback */, (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
} else {
if (!evictWct.isEmpty()) {
wct.merge(evictWct, true /* transfer */);
@@ -434,28 +433,25 @@
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.sendPendingIntent(intent, fillInIntent, options);
+
+ // If split screen is not activated, we're expecting to open a pair of apps to split.
+ final int transitType = mMainStage.isActive()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionConsumed(boolean aborted) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted
- && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
+ mSplitTransitions.startEnterTransition(transitType, wct, null, this,
+ aborted -> {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePositionAnimated(
+ SplitLayout.reversePosition(mSideStagePosition));
}
-
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ } /* consumedCallback */,
+ (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
}
/** Launches an activity into split by legacy transition. */
@@ -564,9 +560,9 @@
/**
* Starts with the second task to a split pair in one transition.
*
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
- * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, float splitRatio,
@@ -592,7 +588,7 @@
wct.startTask(mainTaskId, mainOptions);
mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
setEnterInstanceId(instanceId);
}
@@ -639,7 +635,7 @@
}
/**
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
@@ -1082,15 +1078,15 @@
switch (exitReason) {
// One of the apps doesn't support MW
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
- // User has explicitly dragged the divider to dismiss split
+ // User has explicitly dragged the divider to dismiss split
case EXIT_REASON_DRAG_DIVIDER:
- // Either of the split apps have finished
+ // Either of the split apps have finished
case EXIT_REASON_APP_FINISHED:
- // One of the children enters PiP
+ // One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
- // One of the apps occludes lock screen.
+ // One of the apps occludes lock screen.
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
- // User has unlocked the device after folded
+ // User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
return true;
default:
@@ -1839,7 +1835,7 @@
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionCallback);
+ mRecentTransitionFinishedCallback);
} else if (mSplitTransitions.mPendingRecent == null) {
// If split-task is not controlled by recents animation
// and occluded by the other fullscreen task, dismiss both.
@@ -1853,8 +1849,8 @@
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
- mSplitTransitions.setEnterTransition(
- transition, request.getRemoteTransition(), null /* callback */);
+ mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+ null /* consumedCallback */, null /* finishedCallback */);
}
}
return out;
@@ -1873,7 +1869,7 @@
}
final @WindowManager.TransitionType int type = request.getType();
if (isSplitActive() && !isOpeningType(type)
- && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became "
+ "empty during a mixed transition (one not handled by split),"
+ " so make sure split-screen state is cleaned-up. "
@@ -2031,17 +2027,21 @@
}
}
- // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before
- // applying anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
- throw new IllegalStateException("Launched a task in split, but didn't receive any"
- + " task in transition.");
+ Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+ mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */);
+ return true;
}
} else {
if (mainChild == null || sideChild == null) {
- throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
+ final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
+ (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
+ mSplitTransitions.mPendingEnter.cancel(
+ (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ return true;
}
}
@@ -2305,7 +2305,7 @@
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
- mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
+ mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
mSplitUnsupportedToast.show();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9c2c2fa..af79386 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -619,12 +619,13 @@
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
- va.addUpdateListener(animation -> {
+ final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
position, cornerRadius, clipRect);
- });
+ };
+ va.addUpdateListener(updateListener);
final Runnable finisher = () -> {
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
@@ -637,20 +638,30 @@
});
};
va.addListener(new AnimatorListenerAdapter() {
+ // It is possible for the end/cancel to be called more than once, which may cause
+ // issues if the animating surface has already been released. Track the finished
+ // state here to skip duplicate callbacks. See b/252872225.
private boolean mFinished = false;
@Override
public void onAnimationEnd(Animator animation) {
- if (mFinished) return;
- mFinished = true;
- finisher.run();
+ onFinish();
}
@Override
public void onAnimationCancel(Animator animation) {
+ onFinish();
+ }
+
+ private void onFinish() {
if (mFinished) return;
mFinished = true;
finisher.run();
+ // The update listener can continue to be called after the animation has ended if
+ // end() is called manually again before the finisher removes the animation.
+ // Remove it manually here to prevent animating a released surface.
+ // See b/252872225.
+ va.removeUpdateListener(updateListener);
}
});
animations.add(va);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index db1f19a..3de0911 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -122,6 +122,8 @@
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+ private boolean mIsRegistered = false;
+
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -163,19 +165,18 @@
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
mShellController = shellController;
- shellInit.addInitCallback(this::onInit, this);
- }
-
- private void onInit() {
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- this::createExternalInterface, this);
-
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
ContentResolver resolver = mContext.getContentResolver();
mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
@@ -186,13 +187,23 @@
new SettingsObserver());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mIsRegistered = true;
// Register this transition handler with Core
- mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ try {
+ mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ } catch (RuntimeException e) {
+ mIsRegistered = false;
+ throw e;
+ }
// Pre-load the instance.
TransitionMetrics.getInstance();
}
}
+ public boolean isRegistered() {
+ return mIsRegistered;
+ }
+
private float getTransitionAnimationScaleSetting() {
return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index ea0033b..652f9b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -181,7 +181,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator, null);
+ new RemoteTransition(testRemote), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -421,7 +421,7 @@
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 428f2dc..2000d96 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -49,7 +49,6 @@
<style name="DescriptionSummary">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:gravity">center</item>
<item name="android:layout_marginTop">18dp</item>
<item name="android:layout_marginLeft">18dp</item>
<item name="android:layout_marginRight">18dp</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index eb53ea1..950ee21 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -758,23 +758,16 @@
}
public boolean isBusy() {
- for (CachedBluetoothDevice memberDevice : getMemberDevice()) {
- if (isBusyState(memberDevice)) {
- return true;
+ synchronized (mProfileLock) {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ int status = getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTING
+ || status == BluetoothProfile.STATE_DISCONNECTING) {
+ return true;
+ }
}
+ return getBondState() == BluetoothDevice.BOND_BONDING;
}
- return isBusyState(this);
- }
-
- private boolean isBusyState(CachedBluetoothDevice device){
- for (LocalBluetoothProfile profile : device.getProfiles()) {
- int status = device.getProfileConnectionState(profile);
- if (status == BluetoothProfile.STATE_CONNECTING
- || status == BluetoothProfile.STATE_DISCONNECTING) {
- return true;
- }
- }
- return device.getBondState() == BluetoothDevice.BOND_BONDING;
}
private boolean updateProfiles() {
@@ -920,7 +913,14 @@
@Override
public String toString() {
- return mDevice.toString();
+ return "CachedBluetoothDevice ("
+ + "anonymizedAddress="
+ + mDevice.getAnonymizedAddress()
+ + ", name="
+ + getName()
+ + ", groupId="
+ + mGroupId
+ + ")";
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 8a9f9dd..fb861da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -231,7 +231,7 @@
if (DEBUG) {
Log.d(TAG, "Adding local Volume Control profile");
}
- mVolumeControlProfile = new VolumeControlProfile();
+ mVolumeControlProfile = new VolumeControlProfile(mContext, mDeviceManager, this);
// Note: no event handler for VCP, only for being connectable.
mProfileNameMap.put(VolumeControlProfile.NAME, mVolumeControlProfile);
}
@@ -553,6 +553,10 @@
return mCsipSetCoordinatorProfile;
}
+ public VolumeControlProfile getVolumeControlProfile() {
+ return mVolumeControlProfile;
+ }
+
/**
* Fill in a list of LocalBluetoothProfile objects that are supported by
* the local device and the remote device.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 511df28..57867be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -16,18 +16,91 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.RequiresApi;
/**
* VolumeControlProfile handles Bluetooth Volume Control Controller role
*/
public class VolumeControlProfile implements LocalBluetoothProfile {
private static final String TAG = "VolumeControlProfile";
+ private static boolean DEBUG = true;
static final String NAME = "VCP";
// Order of this profile in device profiles list
- private static final int ORDINAL = 23;
+ private static final int ORDINAL = 1;
+
+ private Context mContext;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ private BluetoothVolumeControl mService;
+ private boolean mIsProfileReady;
+
+ // These callbacks run on the main thread.
+ private final class VolumeControlProfileServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service connected");
+ }
+ mService = (BluetoothVolumeControl) proxy;
+ // We just bound to the service, so refresh the UI for any connected
+ // VolumeControlProfile devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ if (DEBUG) {
+ Log.d(TAG, "VolumeControlProfile found new device: " + nextDevice);
+ }
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(VolumeControlProfile.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady = true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service disconnected");
+ }
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady = false;
+ }
+ }
+
+ VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mContext = context;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new VolumeControlProfile.VolumeControlProfileServiceListener(),
+ BluetoothProfile.VOLUME_CONTROL);
+ }
@Override
public boolean accessProfileEnabled() {
@@ -39,29 +112,70 @@
return true;
}
+ /**
+ * Get VolumeControlProfile devices matching connection states{
+ *
+ * @return Matching device list
+ * @code BluetoothProfile.STATE_CONNECTED,
+ * @code BluetoothProfile.STATE_CONNECTING,
+ * @code BluetoothProfile.STATE_DISCONNECTING}
+ */
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(
+ new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
@Override
public int getConnectionStatus(BluetoothDevice device) {
- return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle VCP
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
}
@Override
public boolean isEnabled(BluetoothDevice device) {
- return false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
@Override
public int getConnectionPolicy(BluetoothDevice device) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle VCP
+ if (mService == null || device == null) {
+ return CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
}
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- return false;
+ boolean isSuccessful = false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ if (DEBUG) {
+ Log.d(TAG, device.getAnonymizedAddress() + " setEnabled: " + enabled);
+ }
+ if (enabled) {
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ }
+ } else {
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ }
+
+ return isSuccessful;
}
@Override
public boolean isProfileReady() {
- return true;
+ return mIsProfileReady;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 1606540..2614644 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -92,7 +92,8 @@
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
COMPLICATION_TYPE_HOME_CONTROLS,
- COMPLICATION_TYPE_SMARTSPACE
+ COMPLICATION_TYPE_SMARTSPACE,
+ COMPLICATION_TYPE_MEDIA_ENTRY
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComplicationType {
@@ -105,6 +106,7 @@
public static final int COMPLICATION_TYPE_CAST_INFO = 5;
public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
+ public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8;
private final Context mContext;
private final IDreamManager mDreamManager;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 315ab0a..79e9938 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1069,80 +1069,4 @@
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
}
-
- @Test
- public void isBusy_mainDeviceIsConnecting_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_mainDeviceIsBonding_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_memberDeviceIsConnecting_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_memberDeviceIsBonding_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_allDevicesAreNotBusy_returnsNotBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isFalse();
- }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 808ea9e..6d375ac 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -549,7 +549,7 @@
try {
IActivityManager am = ActivityManager.getService();
- Configuration config = am.getConfiguration();
+ final Configuration config = new Configuration();
config.setLocales(merged);
// indicate this isn't some passing default - the user wants this remembered
config.userSetLocale = true;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 528af2e..cd667ca 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -48,6 +48,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -376,8 +377,8 @@
Setting newSetting = new Setting(name, oldSetting.getValue(), null,
oldSetting.getPackageName(), oldSetting.getTag(), false,
oldSetting.getId());
- int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
- newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
+ int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), 0,
+ oldValue, newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
mSettings.put(name, newSetting);
updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
@@ -414,8 +415,9 @@
String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
String newDefaultValue = makeDefault ? value : oldDefaultValue;
- int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value,
- oldDefaultValue, newDefaultValue);
+ int newSize = getNewMemoryUsagePerPackageLocked(packageName,
+ oldValue == null ? name.length() : 0 /* deltaKeySize */,
+ oldValue, value, oldDefaultValue, newDefaultValue);
checkNewMemoryUsagePerPackageLocked(packageName, newSize);
Setting newState;
@@ -559,8 +561,12 @@
}
Setting oldState = mSettings.remove(name);
- int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
- null, oldState.defaultValue, null);
+ if (oldState == null) {
+ return false;
+ }
+ int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName,
+ -name.length() /* deltaKeySize */,
+ oldState.value, null, oldState.defaultValue, null);
FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "",
/* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
@@ -583,15 +589,16 @@
}
Setting setting = mSettings.get(name);
+ if (setting == null) {
+ return false;
+ }
Setting oldSetting = new Setting(setting);
String oldValue = setting.getValue();
String oldDefaultValue = setting.getDefaultValue();
- String newValue = oldDefaultValue;
- String newDefaultValue = oldDefaultValue;
- int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue,
- newValue, oldDefaultValue, newDefaultValue);
+ int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, 0, oldValue,
+ oldDefaultValue, oldDefaultValue, oldDefaultValue);
checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize);
if (!setting.reset()) {
@@ -725,8 +732,8 @@
}
@GuardedBy("mLock")
- private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue,
- String newValue, String oldDefaultValue, String newDefaultValue) {
+ private int getNewMemoryUsagePerPackageLocked(String packageName, int deltaKeySize,
+ String oldValue, String newValue, String oldDefaultValue, String newDefaultValue) {
if (isExemptFromMemoryUsageCap(packageName)) {
return 0;
}
@@ -735,7 +742,7 @@
final int newValueSize = (newValue != null) ? newValue.length() : 0;
final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
- final int deltaSize = newValueSize + newDefaultValueSize
+ final int deltaSize = deltaKeySize + newValueSize + newDefaultValueSize
- oldValueSize - oldDefaultValueSize;
return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
}
@@ -1577,4 +1584,11 @@
}
return false;
}
+
+ @VisibleForTesting
+ public int getMemoryUsage(String packageName) {
+ synchronized (mLock) {
+ return mPackageToMemoryUsage.getOrDefault(packageName, 0);
+ }
+ }
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 66b809a..f6d4329 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -295,7 +295,7 @@
settingsState.deleteSettingLocked(SETTING_NAME);
// Should not throw if usage is under the cap
- settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999),
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19975),
null, false, "p1");
settingsState.deleteSettingLocked(SETTING_NAME);
try {
@@ -313,5 +313,97 @@
assertTrue(ex.getMessage().contains("p1"));
}
assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull());
+ try {
+ settingsState.insertSettingLocked(Strings.repeat("A", 20001), "",
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("You are adding too many system settings"));
+ }
+ }
+
+ public void testMemoryUsagePerPackage() {
+ SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+
+ // Test inserting one key with default
+ final String testKey1 = SETTING_NAME;
+ final String testValue1 = Strings.repeat("A", 100);
+ settingsState.insertSettingLocked(testKey1, testValue1, null, true, TEST_PACKAGE);
+ int expectedMemUsage = testKey1.length() + testValue1.length()
+ + testValue1.length() /* size for default */;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test inserting another key
+ final String testKey2 = SETTING_NAME + "2";
+ settingsState.insertSettingLocked(testKey2, testValue1, null, false, TEST_PACKAGE);
+ expectedMemUsage += testKey2.length() + testValue1.length();
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test updating first key with new default
+ final String testValue2 = Strings.repeat("A", 300);
+ settingsState.insertSettingLocked(testKey1, testValue2, null, true, TEST_PACKAGE);
+ expectedMemUsage += (testValue2.length() - testValue1.length()) * 2;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test updating first key without new default
+ final String testValue3 = Strings.repeat("A", 50);
+ settingsState.insertSettingLocked(testKey1, testValue3, null, false, TEST_PACKAGE);
+ expectedMemUsage -= testValue2.length() - testValue3.length();
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test updating second key
+ settingsState.insertSettingLocked(testKey2, testValue2, null, false, TEST_PACKAGE);
+ expectedMemUsage -= testValue1.length() - testValue2.length();
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test resetting key
+ settingsState.resetSettingLocked(testKey1);
+ expectedMemUsage += testValue2.length() - testValue3.length();
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test resetting default value
+ settingsState.resetSettingDefaultValueLocked(testKey1);
+ expectedMemUsage -= testValue2.length();
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test deletion
+ settingsState.deleteSettingLocked(testKey2);
+ expectedMemUsage -= testValue2.length() + testKey2.length() /* key is deleted too */;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test another package with a different key
+ final String testPackage2 = TEST_PACKAGE + "2";
+ final String testKey3 = SETTING_NAME + "3";
+ settingsState.insertSettingLocked(testKey3, testValue1, null, true, testPackage2);
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ final int expectedMemUsage2 = testKey3.length() + testValue1.length() * 2;
+ assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
+
+ // Test system package
+ settingsState.insertSettingLocked(testKey1, testValue1, null, true, SYSTEM_PACKAGE);
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
+ assertEquals(0, settingsState.getMemoryUsage(SYSTEM_PACKAGE));
+
+ // Test invalid value
+ try {
+ settingsState.insertSettingLocked(testKey1, Strings.repeat("A", 20001), null, false,
+ TEST_PACKAGE);
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("You are adding too many system settings"));
+ }
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test invalid key
+ try {
+ settingsState.insertSettingLocked(Strings.repeat("A", 20001), "", null, false,
+ TEST_PACKAGE);
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("You are adding too many system settings"));
+ }
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b5145f9..4267ba2 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -289,6 +289,12 @@
<!-- Query all packages on device on R+ -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <queries>
+ <intent>
+ <action android:name="android.intent.action.NOTES" />
+ </intent>
+ </queries>
+
<!-- Permission to register process observer -->
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 38d636d7..95b986f 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -8,7 +8,7 @@
### Step 1: create a new quick affordance config
* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
* The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
* It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
* When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
diff --git a/packages/SystemUI/res/drawable/overlay_badge_background.xml b/packages/SystemUI/res/drawable/overlay_badge_background.xml
new file mode 100644
index 0000000..857632e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/overlay_badge_background.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="oval">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+</shape>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 2fb6d6c..9fc3f40 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -23,6 +23,7 @@
android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:background="@android:color/transparent"
+ android:importantForAccessibility="no"
android:baselineAligned="false"
android:clickable="false"
android:clipChildren="false"
@@ -56,7 +57,7 @@
android:clipToPadding="false"
android:focusable="true"
android:paddingBottom="@dimen/qqs_layout_padding_bottom"
- android:importantForAccessibility="yes">
+ android:importantForAccessibility="no">
</com.android.systemui.qs.QuickQSPanel>
</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index 60bc373..8b5d953 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -25,6 +25,7 @@
android:gravity="center"
android:layout_gravity="top"
android:orientation="horizontal"
+ android:importantForAccessibility="no"
android:clickable="true"
android:minHeight="48dp">
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 9c02749..1ac78d4 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -103,8 +103,18 @@
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview_border">
- </ImageView>
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview_border"/>
+ <ImageView
+ android:id="@+id/screenshot_badge"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:padding="4dp"
+ android:visibility="gone"
+ android:background="@drawable/overlay_badge_background"
+ android:elevation="8dp"
+ android:src="@drawable/overlay_cancel"
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
<FrameLayout
android:id="@+id/screenshot_dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 92ef3f8..159323a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -42,12 +42,6 @@
android:clipToPadding="false"
android:clipChildren="false">
- <ViewStub
- android:id="@+id/qs_header_stub"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- />
-
<include
layout="@layout/keyguard_status_view"
android:visibility="gone"/>
@@ -69,6 +63,15 @@
systemui:layout_constraintBottom_toBottomOf="parent"
/>
+ <!-- This view should be after qs_frame so touches are dispatched first to it. That gives
+ it a chance to capture clicks before the NonInterceptingScrollView disallows all
+ intercepts -->
+ <ViewStub
+ android:id="@+id/qs_header_stub"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ />
+
<androidx.constraintlayout.widget.Guideline
android:id="@+id/qs_edge_guideline"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9188ce0..93982cb 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -643,6 +643,18 @@
<item>26</item> <!-- MOUTH_COVERING_DETECTED -->
</integer-array>
+ <!-- Which device wake-ups will trigger face auth. These values correspond with
+ PowerManager#WakeReason. -->
+ <integer-array name="config_face_auth_wake_up_triggers">
+ <item>1</item> <!-- WAKE_REASON_POWER_BUTTON -->
+ <item>4</item> <!-- WAKE_REASON_GESTURE -->
+ <item>6</item> <!-- WAKE_REASON_WAKE_KEY -->
+ <item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
+ <item>9</item> <!-- WAKE_REASON_LID -->
+ <item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
+ <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
+ </integer-array>
+
<!-- Whether the communal service should be enabled -->
<bool name="config_communalServiceEnabled">false</bool>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index cd27263..48821e8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -18,7 +18,6 @@
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Handler
-import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import com.android.internal.annotations.Keep
@@ -39,15 +38,15 @@
val context: Context,
val pluginManager: PluginManager,
val handler: Handler,
- defaultClockProvider: ClockProvider
+ val isEnabled: Boolean,
+ userHandle: Int,
+ defaultClockProvider: ClockProvider,
) {
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
fun onClockChanged()
}
- var isEnabled: Boolean = false
-
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
@@ -97,14 +96,19 @@
)
}
- pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java,
- true /* allowMultiple */)
- context.contentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
- false,
- settingObserver,
- UserHandle.USER_ALL
- )
+ if (isEnabled) {
+ pluginManager.addPluginListener(
+ pluginListener,
+ ClockProviderPlugin::class.java,
+ /*allowMultiple=*/ true
+ )
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ /*notifyForDescendants=*/ false,
+ settingObserver,
+ userHandle
+ )
+ }
}
private fun connectClocks(provider: ClockProvider) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7e42e1b..8ac1de8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -85,13 +85,12 @@
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset
- // source rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (sourceRectHint.isEmpty() || sourceRectHint.width() == sourceBounds.width()) {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
} else {
// scale by sourceRectHint if it's not edge-to-edge
final float endScale = sourceRectHint.width() <= sourceRectHint.height()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 8d1768c..e1e8063 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -26,6 +26,7 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -228,7 +229,8 @@
public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
return wrap(info, t, leashMap, (change, taskInfo) -> (taskInfo == null)
- && wallpapers == ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0));
+ && wallpapers == change.hasFlags(FLAG_IS_WALLPAPER)
+ && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
}
private static RemoteAnimationTarget[] wrap(TransitionInfo info,
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index bb3df8f..7b216017 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,17 +18,15 @@
import android.content.Context
import android.os.Handler
-import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
import dagger.Binds
import dagger.Module
import dagger.Provides
-import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class,
SettingsUtilModule::class,
])
@@ -43,20 +41,5 @@
fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
return FlagManager(context, handler)
}
-
- @JvmStatic
- @Provides
- @Named(ALL_FLAGS)
- fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 0f7e732..aef8876 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,29 +16,15 @@
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Binds
import dagger.Module
-import dagger.Provides
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class
])
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
-
- @Module
- companion object {
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 386c095..40a96b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -221,8 +222,11 @@
disposableHandle = parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForDozing(this)
- listenForDozeAmount(this)
- listenForDozeAmountTransition(this)
+ if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ listenForDozeAmountTransition(this)
+ } else {
+ listenForDozeAmount(this)
+ }
}
}
}
@@ -265,10 +269,9 @@
@VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.aodToLockscreenTransition.collect {
- // Would eventually run this:
- // dozeAmount = it.value
- // clock?.animations?.doze(dozeAmount)
+ keyguardTransitionInteractor.dozeAmountTransition.collect {
+ dozeAmount = it.value
+ clock?.animations?.doze(dozeAmount)
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 6fcb6f5..4a41b3f 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -17,6 +17,7 @@
package com.android.keyguard
import android.annotation.StringDef
+import android.os.PowerManager
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
@@ -122,122 +123,93 @@
"Face auth started/stopped because biometric is enabled on keyguard"
}
-/** UiEvents that are logged to identify why face auth is being triggered. */
-enum class FaceAuthUiEvent constructor(private val id: Int, val reason: String) :
+/**
+ * UiEvents that are logged to identify why face auth is being triggered.
+ * @param extraInfo is logged as the position. See [UiEventLogger#logWithInstanceIdAndPosition]
+ */
+enum class FaceAuthUiEvent
+constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
UiEventLogger.UiEventEnum {
@UiEvent(doc = OCCLUDING_APP_REQUESTED)
FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED(1146, OCCLUDING_APP_REQUESTED),
-
@UiEvent(doc = UDFPS_POINTER_DOWN)
FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN(1147, UDFPS_POINTER_DOWN),
-
@UiEvent(doc = SWIPE_UP_ON_BOUNCER)
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER(1148, SWIPE_UP_ON_BOUNCER),
-
@UiEvent(doc = DEVICE_WOKEN_UP_ON_REACH_GESTURE)
FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD(1149, DEVICE_WOKEN_UP_ON_REACH_GESTURE),
-
@UiEvent(doc = FACE_LOCKOUT_RESET)
FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET(1150, FACE_LOCKOUT_RESET),
-
- @UiEvent(doc = QS_EXPANDED)
- FACE_AUTH_TRIGGERED_QS_EXPANDED(1151, QS_EXPANDED),
-
+ @UiEvent(doc = QS_EXPANDED) FACE_AUTH_TRIGGERED_QS_EXPANDED(1151, QS_EXPANDED),
@UiEvent(doc = NOTIFICATION_PANEL_CLICKED)
FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED(1152, NOTIFICATION_PANEL_CLICKED),
-
@UiEvent(doc = PICK_UP_GESTURE_TRIGGERED)
FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED(1153, PICK_UP_GESTURE_TRIGGERED),
-
@UiEvent(doc = ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
- FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN(1154,
- ALTERNATE_BIOMETRIC_BOUNCER_SHOWN),
-
+ FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN(1154, ALTERNATE_BIOMETRIC_BOUNCER_SHOWN),
@UiEvent(doc = PRIMARY_BOUNCER_SHOWN)
FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN(1155, PRIMARY_BOUNCER_SHOWN),
-
@UiEvent(doc = PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN)
FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN(
1197,
PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
),
-
@UiEvent(doc = RETRY_AFTER_HW_UNAVAILABLE)
FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE(1156, RETRY_AFTER_HW_UNAVAILABLE),
-
- @UiEvent(doc = TRUST_DISABLED)
- FACE_AUTH_TRIGGERED_TRUST_DISABLED(1158, TRUST_DISABLED),
-
- @UiEvent(doc = TRUST_ENABLED)
- FACE_AUTH_STOPPED_TRUST_ENABLED(1173, TRUST_ENABLED),
-
+ @UiEvent(doc = TRUST_DISABLED) FACE_AUTH_TRIGGERED_TRUST_DISABLED(1158, TRUST_DISABLED),
+ @UiEvent(doc = TRUST_ENABLED) FACE_AUTH_STOPPED_TRUST_ENABLED(1173, TRUST_ENABLED),
@UiEvent(doc = KEYGUARD_OCCLUSION_CHANGED)
FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED(1159, KEYGUARD_OCCLUSION_CHANGED),
-
@UiEvent(doc = ASSISTANT_VISIBILITY_CHANGED)
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED(1160, ASSISTANT_VISIBILITY_CHANGED),
-
@UiEvent(doc = STARTED_WAKING_UP)
- FACE_AUTH_UPDATED_STARTED_WAKING_UP(1161, STARTED_WAKING_UP),
-
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP(1161, STARTED_WAKING_UP) {
+ override fun extraInfoToString(): String {
+ return PowerManager.wakeReasonToString(extraInfo)
+ }
+ },
+ @Deprecated(
+ "Not a face auth trigger.",
+ ReplaceWith(
+ "FACE_AUTH_UPDATED_STARTED_WAKING_UP, " +
+ "extraInfo=PowerManager.WAKE_REASON_DREAM_FINISHED"
+ )
+ )
@UiEvent(doc = DREAM_STOPPED)
FACE_AUTH_TRIGGERED_DREAM_STOPPED(1162, DREAM_STOPPED),
-
@UiEvent(doc = ALL_AUTHENTICATORS_REGISTERED)
FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED(1163, ALL_AUTHENTICATORS_REGISTERED),
-
@UiEvent(doc = ENROLLMENTS_CHANGED)
FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED(1164, ENROLLMENTS_CHANGED),
-
@UiEvent(doc = KEYGUARD_VISIBILITY_CHANGED)
FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED(1165, KEYGUARD_VISIBILITY_CHANGED),
-
@UiEvent(doc = FACE_CANCEL_NOT_RECEIVED)
FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED(1174, FACE_CANCEL_NOT_RECEIVED),
-
@UiEvent(doc = AUTH_REQUEST_DURING_CANCELLATION)
FACE_AUTH_TRIGGERED_DURING_CANCELLATION(1175, AUTH_REQUEST_DURING_CANCELLATION),
-
- @UiEvent(doc = DREAM_STARTED)
- FACE_AUTH_STOPPED_DREAM_STARTED(1176, DREAM_STARTED),
-
- @UiEvent(doc = FP_LOCKED_OUT)
- FACE_AUTH_STOPPED_FP_LOCKED_OUT(1177, FP_LOCKED_OUT),
-
+ @UiEvent(doc = DREAM_STARTED) FACE_AUTH_STOPPED_DREAM_STARTED(1176, DREAM_STARTED),
+ @UiEvent(doc = FP_LOCKED_OUT) FACE_AUTH_STOPPED_FP_LOCKED_OUT(1177, FP_LOCKED_OUT),
@UiEvent(doc = FACE_AUTH_STOPPED_ON_USER_INPUT)
FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER(1178, FACE_AUTH_STOPPED_ON_USER_INPUT),
-
@UiEvent(doc = KEYGUARD_GOING_AWAY)
FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY(1179, KEYGUARD_GOING_AWAY),
-
- @UiEvent(doc = CAMERA_LAUNCHED)
- FACE_AUTH_UPDATED_CAMERA_LAUNCHED(1180, CAMERA_LAUNCHED),
-
- @UiEvent(doc = FP_AUTHENTICATED)
- FACE_AUTH_UPDATED_FP_AUTHENTICATED(1181, FP_AUTHENTICATED),
-
- @UiEvent(doc = GOING_TO_SLEEP)
- FACE_AUTH_UPDATED_GOING_TO_SLEEP(1182, GOING_TO_SLEEP),
-
+ @UiEvent(doc = CAMERA_LAUNCHED) FACE_AUTH_UPDATED_CAMERA_LAUNCHED(1180, CAMERA_LAUNCHED),
+ @UiEvent(doc = FP_AUTHENTICATED) FACE_AUTH_UPDATED_FP_AUTHENTICATED(1181, FP_AUTHENTICATED),
+ @UiEvent(doc = GOING_TO_SLEEP) FACE_AUTH_UPDATED_GOING_TO_SLEEP(1182, GOING_TO_SLEEP),
@UiEvent(doc = FINISHED_GOING_TO_SLEEP)
FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP(1183, FINISHED_GOING_TO_SLEEP),
-
- @UiEvent(doc = KEYGUARD_INIT)
- FACE_AUTH_UPDATED_ON_KEYGUARD_INIT(1189, KEYGUARD_INIT),
-
- @UiEvent(doc = KEYGUARD_RESET)
- FACE_AUTH_UPDATED_KEYGUARD_RESET(1185, KEYGUARD_RESET),
-
- @UiEvent(doc = USER_SWITCHING)
- FACE_AUTH_UPDATED_USER_SWITCHING(1186, USER_SWITCHING),
-
+ @UiEvent(doc = KEYGUARD_INIT) FACE_AUTH_UPDATED_ON_KEYGUARD_INIT(1189, KEYGUARD_INIT),
+ @UiEvent(doc = KEYGUARD_RESET) FACE_AUTH_UPDATED_KEYGUARD_RESET(1185, KEYGUARD_RESET),
+ @UiEvent(doc = USER_SWITCHING) FACE_AUTH_UPDATED_USER_SWITCHING(1186, USER_SWITCHING),
@UiEvent(doc = FACE_AUTHENTICATED)
FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED(1187, FACE_AUTHENTICATED),
-
@UiEvent(doc = BIOMETRIC_ENABLED)
FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED);
override fun getId(): Int = this.id
+
+ /** Convert [extraInfo] to a human-readable string. By default, this is empty. */
+ open fun extraInfoToString(): String = ""
}
private val apiRequestReasonToUiEvent =
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
new file mode 100644
index 0000000..a0c43fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.content.res.Resources
+import android.os.Build
+import android.os.PowerManager
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.GlobalSettings
+import java.io.PrintWriter
+import java.util.stream.Collectors
+import javax.inject.Inject
+
+/** Determines which device wake-ups should trigger face authentication. */
+@SysUISingleton
+class FaceWakeUpTriggersConfig
+@Inject
+constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpManager: DumpManager) :
+ Dumpable {
+ private val defaultTriggerFaceAuthOnWakeUpFrom: Set<Int> =
+ resources.getIntArray(R.array.config_face_auth_wake_up_triggers).toSet()
+ private val triggerFaceAuthOnWakeUpFrom: Set<Int>
+
+ init {
+ triggerFaceAuthOnWakeUpFrom =
+ if (Build.IS_DEBUGGABLE) {
+ // Update face wake triggers via adb on debuggable builds:
+ // ie: adb shell settings put global face_wake_triggers "1\|4" &&
+ // adb shell am crash com.android.systemui
+ processStringArray(
+ globalSettings.getString("face_wake_triggers"),
+ defaultTriggerFaceAuthOnWakeUpFrom
+ )
+ } else {
+ defaultTriggerFaceAuthOnWakeUpFrom
+ }
+ dumpManager.registerDumpable(this)
+ }
+
+ fun shouldTriggerFaceAuthOnWakeUpFrom(@PowerManager.WakeReason pmWakeReason: Int): Boolean {
+ return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("FaceWakeUpTriggers:")
+ for (pmWakeReason in triggerFaceAuthOnWakeUpFrom) {
+ pw.println(" ${PowerManager.wakeReasonToString(pmWakeReason)}")
+ }
+ }
+
+ /** Convert a pipe-separated set of integers into a set of ints. */
+ private fun processStringArray(stringSetting: String?, default: Set<Int>): Set<Int> {
+ return stringSetting?.let {
+ stringSetting.split("|").stream().map(Integer::parseInt).collect(Collectors.toSet())
+ }
+ ?: default
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 35eecdf..d3cc7ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -37,8 +37,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
@@ -120,8 +118,7 @@
SecureSettings secureSettings,
@Main Executor uiExecutor,
DumpManager dumpManager,
- ClockEventController clockEventController,
- FeatureFlags featureFlags) {
+ ClockEventController clockEventController) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mClockRegistry = clockRegistry;
@@ -134,7 +131,6 @@
mDumpManager = dumpManager;
mClockEventController = clockEventController;
- mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS));
mClockChangedListener = () -> {
setClock(mClockRegistry.createCurrentClock());
};
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 81305f9..0b395a8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -223,7 +223,7 @@
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
- boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
mKeyguardSecurityCallback.userActivity();
if (didFaceAuthRun) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index cb1330d..bad75e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -44,7 +44,6 @@
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DREAM_STOPPED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DURING_CANCELLATION;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET;
@@ -303,6 +302,7 @@
}
}
};
+ private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
@@ -1631,7 +1631,7 @@
@Override
public void onUdfpsPointerDown(int sensorId) {
mLogger.logUdfpsPointerDown(sensorId);
- requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
/**
@@ -1823,11 +1823,21 @@
}
}
- protected void handleStartedWakingUp() {
+ protected void handleStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
Assert.isMainThread();
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_STARTED_WAKING_UP);
- requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp");
+
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) {
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
+ updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP);
+ requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp - "
+ + PowerManager.wakeReasonToString(pmWakeReason));
+ } else {
+ mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
+ }
+
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1879,12 +1889,9 @@
cb.onDreamingStateChanged(mIsDreaming);
}
}
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
if (mIsDreaming) {
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_DREAM_STARTED);
- } else {
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_DREAM_STOPPED);
}
}
@@ -1964,7 +1971,8 @@
PackageManager packageManager,
@Nullable FaceManager faceManager,
@Nullable FingerprintManager fingerprintManager,
- @Nullable BiometricManager biometricManager) {
+ @Nullable BiometricManager biometricManager,
+ FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mTelephonyListenerManager = telephonyListenerManager;
@@ -2003,6 +2011,7 @@
R.array.config_face_acquire_device_entry_ignorelist))
.boxed()
.collect(Collectors.toSet());
+ mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
mHandler = new Handler(mainLooper) {
@Override
@@ -2052,7 +2061,7 @@
break;
case MSG_STARTED_WAKING_UP:
Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
- handleStartedWakingUp();
+ handleStartedWakingUp(msg.arg1);
Trace.endSection();
break;
case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
@@ -2363,14 +2372,12 @@
/**
* Requests face authentication if we're on a state where it's allowed.
* This will re-trigger auth in case it fails.
- * @param userInitiatedRequest true if the user explicitly requested face auth
* @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
* invoked.
* @return current face auth detection state, true if it is running.
*/
- public boolean requestFaceAuth(boolean userInitiatedRequest,
- @FaceAuthApiRequestReason String reason) {
- mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
+ public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
+ mLogger.logFaceAuthRequested(reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
return isFaceDetectionRunning();
}
@@ -2799,8 +2806,14 @@
// Waiting for ERROR_CANCELED before requesting auth again
return;
}
- mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
- mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
+ mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent);
+ mUiEventLogger.logWithInstanceIdAndPosition(
+ faceAuthUiEvent,
+ 0,
+ null,
+ getKeyguardSessionId(),
+ faceAuthUiEvent.getExtraInfo()
+ );
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
@@ -3579,11 +3592,16 @@
// TODO: use these callbacks elsewhere in place of the existing notifyScreen*()
// (KeyguardViewMediator, KeyguardHostView)
- public void dispatchStartedWakingUp() {
+ /**
+ * Dispatch wakeup events to:
+ * - update biometric listening states
+ * - send to registered KeyguardUpdateMonitorCallbacks
+ */
+ public void dispatchStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
synchronized (this) {
mDeviceInteractive = true;
}
- mHandler.sendEmptyMessage(MSG_STARTED_WAKING_UP);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_WAKING_UP, pmWakeReason, 0));
}
public void dispatchStartedGoingToSleep(int why) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index c41b752..fe7c70a 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -23,6 +23,8 @@
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -44,6 +46,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.systemui.Dumpable;
@@ -53,6 +56,10 @@
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -65,6 +72,7 @@
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -101,6 +109,9 @@
@NonNull private CharSequence mLockedLabel;
@NonNull private final VibratorHelper mVibrator;
@Nullable private final AuthRippleController mAuthRippleController;
+ @NonNull private final FeatureFlags mFeatureFlags;
+ @NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
+ @NonNull private final KeyguardInteractor mKeyguardInteractor;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
private VelocityTracker mVelocityTracker;
@@ -137,6 +148,20 @@
private boolean mDownDetected;
private final Rect mSensorTouchLocation = new Rect();
+ @VisibleForTesting
+ final Consumer<TransitionStep> mDozeTransitionCallback = (TransitionStep step) -> {
+ mInterpolatedDarkAmount = step.getValue();
+ mView.setDozeAmount(step.getValue());
+ updateBurnInOffsets();
+ };
+
+ @VisibleForTesting
+ final Consumer<Boolean> mIsDozingCallback = (Boolean isDozing) -> {
+ mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateVisibility();
+ };
+
@Inject
public LockIconViewController(
@Nullable LockIconView view,
@@ -152,7 +177,10 @@
@NonNull @Main DelayableExecutor executor,
@NonNull VibratorHelper vibrator,
@Nullable AuthRippleController authRippleController,
- @NonNull @Main Resources resources
+ @NonNull @Main Resources resources,
+ @NonNull KeyguardTransitionInteractor transitionInteractor,
+ @NonNull KeyguardInteractor keyguardInteractor,
+ @NonNull FeatureFlags featureFlags
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -166,6 +194,9 @@
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mTransitionInteractor = transitionInteractor;
+ mKeyguardInteractor = keyguardInteractor;
+ mFeatureFlags = featureFlags;
mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -182,6 +213,12 @@
@Override
protected void onInit() {
mView.setAccessibilityDelegate(mAccessibilityDelegate);
+
+ if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ collectFlow(mView, mTransitionInteractor.getDozeAmountTransition(),
+ mDozeTransitionCallback);
+ collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback);
+ }
}
@Override
@@ -377,14 +414,17 @@
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon);
- pw.println(" mIsDozing: " + mIsDozing);
- pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
- pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
- pw.println(" mRunningFPS: " + mRunningFPS);
- pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
- pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
- pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
- pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
+ pw.println();
+ pw.println(" mIsDozing: " + mIsDozing);
+ pw.println(" isFlagEnabled(DOZING_MIGRATION_1): "
+ + mFeatureFlags.isEnabled(DOZING_MIGRATION_1));
+ pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
+ pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
+ pw.println(" mRunningFPS: " + mRunningFPS);
+ pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
+ pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
+ pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
+ pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
if (mView != null) {
mView.dump(pw, args);
@@ -425,16 +465,20 @@
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
- mInterpolatedDarkAmount = eased;
- mView.setDozeAmount(eased);
- updateBurnInOffsets();
+ if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ mInterpolatedDarkAmount = eased;
+ mView.setDozeAmount(eased);
+ updateBurnInOffsets();
+ }
}
@Override
public void onDozingChanged(boolean isDozing) {
- mIsDozing = isDozing;
- updateBurnInOffsets();
- updateVisibility();
+ if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateVisibility();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index f43f559..9767313 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -18,10 +18,13 @@
import android.content.Context;
import android.os.Handler;
+import android.os.UserHandle;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
import com.android.systemui.shared.plugins.PluginManager;
@@ -39,7 +42,14 @@
@Application Context context,
PluginManager pluginManager,
@Main Handler handler,
- DefaultClockProvider defaultClockProvider) {
- return new ClockRegistry(context, pluginManager, handler, defaultClockProvider);
+ DefaultClockProvider defaultClockProvider,
+ FeatureFlags featureFlags) {
+ return new ClockRegistry(
+ context,
+ pluginManager,
+ handler,
+ featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
+ UserHandle.USER_ALL,
+ defaultClockProvider);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2f79e30..3308f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -17,9 +17,12 @@
package com.android.keyguard.logging
import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.os.PowerManager
+import android.os.PowerManager.WakeReason
import android.telephony.ServiceState
import android.telephony.SubscriptionInfo
import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.plugins.log.LogBuffer
@@ -108,11 +111,10 @@
}, { "Face help received, msgId: $int1 msg: $str1" })
}
- fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String?) {
+ fun logFaceAuthRequested(reason: String?) {
logBuffer.log(TAG, DEBUG, {
- bool1 = userInitiatedRequest
str1 = reason
- }, { "requestFaceAuth() userInitiated=$bool1 reason=$str1" })
+ }, { "requestFaceAuth() reason=$str1" })
}
fun logFaceAuthSuccess(userId: Int) {
@@ -269,11 +271,19 @@
logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
}
- fun logStartedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
+ fun logStartedListeningForFace(faceRunningState: Int, faceAuthUiEvent: FaceAuthUiEvent) {
logBuffer.log(TAG, VERBOSE, {
int1 = faceRunningState
- str1 = faceAuthReason
- }, { "startListeningForFace(): $int1, reason: $str1" })
+ str1 = faceAuthUiEvent.reason
+ str2 = faceAuthUiEvent.extraInfoToString()
+ }, { "startListeningForFace(): $int1, reason: $str1 $str2" })
+ }
+
+ fun logStartedListeningForFaceFromWakeUp(faceRunningState: Int, @WakeReason pmWakeReason: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ int1 = faceRunningState
+ str1 = PowerManager.wakeReasonToString(pmWakeReason)
+ }, { "startListeningForFace(): $int1, reason: wakeUp-$str1" })
}
fun logStoppedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
@@ -383,4 +393,10 @@
}, { "#update secure=$bool1 canDismissKeyguard=$bool2" +
" trusted=$bool3 trustManaged=$bool4" })
}
+
+ fun logSkipUpdateFaceListeningOnWakeup(@WakeReason pmWakeReason: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = PowerManager.wakeReasonToString(pmWakeReason)
+ }, { "Skip updating face listening state on wakeup from $str1"})
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b49d452..96fe65f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -859,9 +859,7 @@
playStartHaptic();
if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
- mKeyguardUpdateMonitor.requestFaceAuth(
- /* userInitiatedRequest */ false,
- FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
}
mOnFingerDown = true;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7e31626..6db56210 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,6 +48,7 @@
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
@@ -83,6 +84,7 @@
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.statusbar.window.StatusBarWindowModule;
import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
+import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.unfold.SysUIUnfoldModule;
import com.android.systemui.user.UserModule;
@@ -149,9 +151,11 @@
SysUIConcurrencyModule.class,
SysUIUnfoldModule.class,
TelephonyRepositoryModule.class,
+ TemporaryDisplayModule.class,
TunerModule.class,
UserModule.class,
UtilModule.class,
+ NoteTaskModule.class,
WalletModule.class
},
subcomponents = {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 29bb2f4..41f5578 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -164,7 +164,8 @@
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
COMPLICATION_TYPE_HOME_CONTROLS,
- COMPLICATION_TYPE_SMARTSPACE
+ COMPLICATION_TYPE_SMARTSPACE,
+ COMPLICATION_TYPE_MEDIA_ENTRY
})
@Retention(RetentionPolicy.SOURCE)
@interface ComplicationType {}
@@ -177,6 +178,7 @@
int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5;
int COMPLICATION_TYPE_SMARTSPACE = 1 << 6;
+ int COMPLICATION_TYPE_MEDIA_ENTRY = 1 << 7;
/**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
index 75a97de..18aacd2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -20,6 +20,7 @@
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
@@ -54,6 +55,8 @@
return COMPLICATION_TYPE_HOME_CONTROLS;
case DreamBackend.COMPLICATION_TYPE_SMARTSPACE:
return COMPLICATION_TYPE_SMARTSPACE;
+ case DreamBackend.COMPLICATION_TYPE_MEDIA_ENTRY:
+ return COMPLICATION_TYPE_MEDIA_ENTRY;
default:
return COMPLICATION_TYPE_NONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
index 1166c2f..deff060 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
@@ -55,6 +55,11 @@
return mComponentFactory.create().getViewHolder();
}
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_MEDIA_ENTRY;
+ }
+
/**
* Contains values/logic associated with the dream complication view.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index b983e5c..20e55a0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -21,6 +21,7 @@
import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
import static com.android.systemui.flags.FlagManager.EXTRA_ID;
import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
import static java.util.Objects.requireNonNull;
@@ -58,20 +59,20 @@
*
* Flags can be set (or unset) via the following adb command:
*
- * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
+ * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
*
- * Alternatively, you can change flags via a broadcast intent:
+ * Alternatively, you can change flags via a broadcast intent:
*
- * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
*
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
public class FeatureFlagsDebug implements FeatureFlags {
static final String TAG = "SysUIFlags";
- static final String ALL_FLAGS = "all_flags";
private final FlagManager mFlagManager;
+ private final Context mContext;
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
@@ -82,6 +83,14 @@
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
private final Restarter mRestarter;
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsDebug(
FlagManager flagManager,
@@ -92,23 +101,28 @@
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
- Restarter barService) {
+ Restarter restarter) {
mFlagManager = flagManager;
+ mContext = context;
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
- mRestarter = barService;
+ mRestarter = restarter;
+ }
+ /** Call after construction to setup listeners. */
+ void init() {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS);
- flagManager.setOnSettingsChangedAction(this::restartSystemUI);
- flagManager.setClearCacheAction(this::removeFromCache);
- context.registerReceiver(mReceiver, filter, null, null,
+ mFlagManager.setOnSettingsChangedAction(this::restartSystemUI);
+ mFlagManager.setClearCacheAction(this::removeFromCache);
+ mContext.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
@@ -195,7 +209,7 @@
return mStringFlagCache.get(id);
}
- /** Specific override for Boolean flags that checks against the teamfood list.*/
+ /** Specific override for Boolean flags that checks against the teamfood list. */
private boolean readFlagValue(int id, boolean defaultValue) {
Boolean result = readBooleanFlagOverride(id);
boolean hasServerOverride = mServerFlagReader.hasOverride(id);
@@ -272,6 +286,7 @@
private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
}
+
/** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
private void eraseInternal(int id) {
// We can't actually "erase" things from sysprops, but we can set them to empty!
@@ -357,7 +372,7 @@
}
}
- Bundle extras = getResultExtras(true);
+ Bundle extras = getResultExtras(true);
if (extras != null) {
extras.putParcelableArrayList(EXTRA_FLAGS, pFlags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 560dcbd..6271334 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -31,7 +31,7 @@
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
- featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlagsDebug
) : CoreStartable {
init {
@@ -41,6 +41,7 @@
}
override fun start() {
+ featureFlags.init()
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 40a8a1a..30cad5f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import static java.util.Objects.requireNonNull;
import android.content.res.Resources;
@@ -34,6 +36,7 @@
import java.util.Map;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* Default implementation of the a Flag manager that returns default values for release builds
@@ -49,26 +52,47 @@
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
+ private final Restarter mRestarter;
+ private final Map<Integer, Flag<?>> mAllFlags;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy,
- ServerFlagReader serverFlagReader) {
+ ServerFlagReader serverFlagReader,
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+ Restarter restarter) {
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
+ mAllFlags = allFlags;
+ mRestarter = restarter;
+ }
+
+ /** Call after construction to setup listeners. */
+ void init() {
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
- public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {}
+ public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {
+ }
@Override
- public void removeListener(@NonNull Listener listener) {}
+ public void removeListener(@NonNull Listener listener) {
+ }
@Override
public boolean isEnabled(@NotNull UnreleasedFlag flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 4d25431..1e93c0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import androidx.annotation.NonNull;
import com.android.systemui.statusbar.commandline.Command;
@@ -42,7 +44,7 @@
@Inject
FlagCommand(
FeatureFlagsDebug featureFlags,
- @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
) {
mFeatureFlags = featureFlags;
mAllFlags = allFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 9ef3f5d..aa0ca20 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -57,7 +57,7 @@
val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
// TODO(b/254512425): Tracking Bug
- val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = false)
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
@@ -118,6 +118,12 @@
*/
@JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+ /**
+ * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
+ * will occur in stages. This is one stage of many to come.
+ */
+ @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -156,9 +162,6 @@
// TODO(b/254513246): Tracking Bug
val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
- // TODO(b/254513025): Tracking Bug
- val STATUS_BAR_LETTERBOX_APPEARANCE = ReleasedFlag(603, teamfood = false)
-
// TODO(b/254512623): Tracking Bug
@Deprecated("Replaced by mobile and wifi specific flags.")
val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
@@ -190,7 +193,7 @@
// 802 - wallpaper rendering
// TODO(b/254512923): Tracking Bug
- @JvmField val USE_CANVAS_RENDERER = ReleasedFlag(802)
+ @JvmField val USE_CANVAS_RENDERER = UnreleasedFlag(802, teamfood = true)
// 803 - screen contents translation
// TODO(b/254513187): Tracking Bug
@@ -224,15 +227,9 @@
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
- // TODO(b/254512444): Tracking Bug
- @JvmField val DOCK_SETUP_ENABLED = ReleasedFlag(1001)
-
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
- // TODO(b/254512525): Tracking Bug
- @JvmField val REFACTORED_DOCK_SETUP = ReleasedFlag(1003, teamfood = true)
-
// 1100 - windowing
@Keep
val WM_ENABLE_SHELL_TRANSITIONS =
@@ -322,6 +319,9 @@
// 1800 - shade container
@JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
+ // 1900 - note task
+ @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
new file mode 100644
index 0000000..e1f4944
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import com.android.internal.statusbar.IStatusBarService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+/** Module containing shared code for all FeatureFlag implementations. */
+@Module
+interface FlagsCommonModule {
+ companion object {
+ const val ALL_FLAGS = "all_flags"
+
+ @JvmStatic
+ @Provides
+ @Named(ALL_FLAGS)
+ fun providesAllFlags(): Map<Int, Flag<*>> {
+ return Flags.collectFlags()
+ }
+
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object : Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index fc5b9f4..694fa01 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -16,9 +16,13 @@
package com.android.systemui.flags
+import android.provider.DeviceConfig
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.DeviceConfigProxy
-import dagger.Binds
import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
import javax.inject.Inject
interface ServerFlagReader {
@@ -27,40 +31,99 @@
/** Returns any stored server-side setting or the default if not set. */
fun readServerOverride(flagId: Int, default: Boolean): Boolean
+
+ /** Register a listener for changes to any of the passed in flags. */
+ fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
+
+ interface ChangeListener {
+ fun onChange()
+ }
}
class ServerFlagReaderImpl @Inject constructor(
- private val deviceConfig: DeviceConfigProxy
+ private val namespace: String,
+ private val deviceConfig: DeviceConfigProxy,
+ @Background private val executor: Executor
) : ServerFlagReader {
+
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
+
+ private val onPropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener {
+ override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ if (properties.namespace != namespace) {
+ return
+ }
+
+ for ((listener, flags) in listeners) {
+ propLoop@ for (propName in properties.keyset) {
+ for (flag in flags) {
+ if (propName == getServerOverrideName(flag.id)) {
+ listener.onChange()
+ break@propLoop
+ }
+ }
+ }
+ }
+ }
+ }
+
override fun hasOverride(flagId: Int): Boolean =
deviceConfig.getProperty(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId)
) != null
override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
return deviceConfig.getBoolean(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId),
default
)
}
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ if (listeners.isEmpty()) {
+ deviceConfig.addOnPropertiesChangedListener(
+ namespace,
+ executor,
+ onPropertiesChangedListener
+ )
+ }
+ listeners.add(Pair(listener, flags))
+ }
+
private fun getServerOverrideName(flagId: Int): String {
return "flag_override_$flagId"
}
}
-private val SYSUI_NAMESPACE = "systemui"
-
@Module
interface ServerFlagReaderModule {
- @Binds
- fun bindsReader(impl: ServerFlagReaderImpl): ServerFlagReader
+ companion object {
+ private val SYSUI_NAMESPACE = "systemui"
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindsReader(
+ deviceConfig: DeviceConfigProxy,
+ @Background executor: Executor
+ ): ServerFlagReader {
+ return ServerFlagReaderImpl(
+ SYSUI_NAMESPACE, deviceConfig, executor
+ )
+ }
+ }
}
class ServerFlagReaderFake : ServerFlagReader {
private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
override fun hasOverride(flagId: Int): Boolean {
return flagMap.containsKey(flagId)
@@ -72,9 +135,24 @@
fun setFlagValue(flagId: Int, value: Boolean) {
flagMap.put(flagId, value)
+
+ for ((listener, flags) in listeners) {
+ flagLoop@ for (flag in flags) {
+ if (flagId == flag.id) {
+ listener.onChange()
+ break@flagLoop
+ }
+ }
+ }
}
fun eraseFlag(flagId: Int) {
flagMap.remove(flagId)
}
+
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index ddcd053..8846bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -554,7 +554,7 @@
@PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
checkPermission();
- mKeyguardViewMediator.onStartedWakingUp(cameraGestureTriggered);
+ mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
mKeyguardLifecyclesDispatcher.dispatch(
KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason);
Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c8370b4..6f1ad70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -91,6 +91,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -322,6 +323,12 @@
// true if the keyguard is hidden by another window
private boolean mOccluded = false;
+ /**
+ * Whether the {@link #mOccludeAnimationController} is currently playing the occlusion
+ * animation.
+ */
+ private boolean mOccludeAnimationPlaying = false;
+
private boolean mWakeAndUnlocking = false;
/**
@@ -335,12 +342,6 @@
*/
private int mDelayedProfileShowingSequence;
- /**
- * If the user has disabled the keyguard, then requests to exit, this is
- * how we'll ultimately let them know whether it was successful. We use this
- * var being non-null as an indicator that there is an in progress request.
- */
- private IKeyguardExitCallback mExitSecureCallback;
private final DismissCallbackRegistry mDismissCallbackRegistry;
// the properties of the keyguard
@@ -836,15 +837,22 @@
/**
* Animation launch controller for activities that occlude the keyguard.
*/
- private final ActivityLaunchAnimator.Controller mOccludeAnimationController =
+ @VisibleForTesting
+ final ActivityLaunchAnimator.Controller mOccludeAnimationController =
new ActivityLaunchAnimator.Controller() {
@Override
- public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {}
+ public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+ mOccludeAnimationPlaying = true;
+ }
@Override
public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) {
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
+ mOccludeAnimationPlaying = false;
+
+ // Ensure keyguard state is set correctly if we're cancelled.
+ mCentralSurfaces.updateIsKeyguard();
}
@Override
@@ -853,6 +861,12 @@
mCentralSurfaces.instantCollapseNotificationPanel();
}
+ mOccludeAnimationPlaying = false;
+
+ // Hide the keyguard now that we're done launching the occluding activity over
+ // it.
+ mCentralSurfaces.updateIsKeyguard();
+
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
}
@@ -1321,18 +1335,7 @@
|| !mLockPatternUtils.isSecure(currentUser);
long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
mLockLater = false;
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
- try {
- mExitSecureCallback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
- mExitSecureCallback = null;
- if (!mExternallyEnabled) {
- hideLocked();
- }
- } else if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
+ if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
// If we are going to sleep but the keyguard is showing (and will continue to be
// showing, not in the process of going away) then reset its state. Otherwise, let
// this fall through and explicitly re-lock the keyguard.
@@ -1574,7 +1577,8 @@
/**
* It will let us know when the device is waking up.
*/
- public void onStartedWakingUp(boolean cameraGestureTriggered) {
+ public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason,
+ boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
// TODO: Rename all screen off/on references to interactive/sleeping
@@ -1589,7 +1593,7 @@
if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
notifyStartedWakingUp();
}
- mUpdateMonitor.dispatchStartedWakingUp();
+ mUpdateMonitor.dispatchStartedWakingUp(pmWakeReason);
maybeSendUserPresentBroadcast();
Trace.endSection();
}
@@ -1651,13 +1655,6 @@
mExternallyEnabled = enabled;
if (!enabled && mShowing) {
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
- // we're in the process of handling a request to verify the user
- // can get past the keyguard. ignore extraneous requests to disable / re-enable
- return;
- }
-
// hiding keyguard that is showing, remember to reshow later
if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ "disabling status bar expansion");
@@ -1671,33 +1668,23 @@
mNeedToReshowWhenReenabled = false;
updateInputRestrictedLocked();
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
- try {
- mExitSecureCallback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
- mExitSecureCallback = null;
- resetStateLocked();
- } else {
- showLocked(null);
+ showLocked(null);
- // block until we know the keyguard is done drawing (and post a message
- // to unblock us after a timeout, so we don't risk blocking too long
- // and causing an ANR).
- mWaitingUntilKeyguardVisible = true;
- mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
- if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
- while (mWaitingUntilKeyguardVisible) {
- try {
- wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
+ // block until we know the keyguard is done drawing (and post a message
+ // to unblock us after a timeout, so we don't risk blocking too long
+ // and causing an ANR).
+ mWaitingUntilKeyguardVisible = true;
+ mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING,
+ KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
+ if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
+ while (mWaitingUntilKeyguardVisible) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
- if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
}
+ if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
}
}
}
@@ -1727,13 +1714,6 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
}
- } else if (mExitSecureCallback != null) {
- // already in progress with someone else
- try {
- callback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
} else if (!isSecure()) {
// Keyguard is not secure, no need to do anything, and we don't need to reshow
@@ -1767,6 +1747,10 @@
return mShowing && !mOccluded;
}
+ public boolean isOccludeAnimationPlaying() {
+ return mOccludeAnimationPlaying;
+ }
+
/**
* Notify us when the keyguard is occluded by another window
*/
@@ -2270,21 +2254,6 @@
return;
}
setPendingLock(false); // user may have authenticated during the screen off animation
- if (mExitSecureCallback != null) {
- try {
- mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult()", e);
- }
-
- mExitSecureCallback = null;
-
- // after successfully exiting securely, no need to reshow
- // the keyguard when they've released the lock
- mExternallyEnabled = true;
- mNeedToReshowWhenReenabled = false;
- updateInputRestricted();
- }
handleHide();
mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
@@ -3093,7 +3062,6 @@
pw.print(" mInputRestricted: "); pw.println(mInputRestricted);
pw.print(" mOccluded: "); pw.println(mOccluded);
pw.print(" mDelayedShowingSequence: "); pw.println(mDelayedShowingSequence);
- pw.print(" mExitSecureCallback: "); pw.println(mExitSecureCallback);
pw.print(" mDeviceInteractive: "); pw.println(mDeviceInteractive);
pw.print(" mGoingToSleep: "); pw.println(mGoingToSleep);
pw.print(" mHiding: "); pw.println(mHiding);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
new file mode 100644
index 0000000..a069582
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+/**
+ * Unique identifier keys for all known built-in quick affordances.
+ *
+ * Please ensure uniqueness by never associating more than one class with each key.
+ */
+object BuiltInKeyguardQuickAffordanceKeys {
+ // Please keep alphabetical order of const names to simplify future maintenance.
+ const val HOME_CONTROLS = "home"
+ const val QR_CODE_SCANNER = "qr_code_scanner"
+ const val QUICK_ACCESS_WALLET = "wallet"
+ // Please keep alphabetical order of const names to simplify future maintenance.
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 8384260..c600e13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
import android.content.Intent
@@ -51,19 +51,21 @@
private val appContext = context.applicationContext
- override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {
stateInternal(component.getControlsListingController().getOrNull())
} else {
- flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
}
- override fun onQuickAffordanceClicked(
+ override fun onTriggered(
expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent =
Intent(appContext, ControlsActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -77,9 +79,9 @@
private fun stateInternal(
listingController: ControlsListingController?,
- ): Flow<KeyguardQuickAffordanceConfig.State> {
+ ): Flow<KeyguardQuickAffordanceConfig.LockScreenState> {
if (listingController == null) {
- return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ return flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
return conflatedCallbackFlow {
@@ -114,7 +116,7 @@
hasServiceInfos: Boolean,
visibility: ControlsComponent.Visibility,
@DrawableRes iconResourceId: Int?,
- ): KeyguardQuickAffordanceConfig.State {
+ ): KeyguardQuickAffordanceConfig.LockScreenState {
return if (
isFeatureEnabled &&
hasFavorites &&
@@ -122,7 +124,7 @@
iconResourceId != null &&
visibility == ControlsComponent.Visibility.AVAILABLE
) {
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
Icon.Resource(
res = iconResourceId,
@@ -133,7 +135,7 @@
),
)
} else {
- KeyguardQuickAffordanceConfig.State.Hidden
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..0a8090b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface that can act as data source for a single quick affordance model. */
+interface KeyguardQuickAffordanceConfig {
+
+ /** Unique identifier for this quick affordance. It must be globally unique. */
+ val key: String
+
+ /**
+ * The ever-changing state of the affordance.
+ *
+ * Used to populate the lock screen.
+ */
+ val lockScreenState: Flow<LockScreenState>
+
+ /**
+ * Notifies that the affordance was clicked by the user.
+ *
+ * @param expandable An [Expandable] to use when animating dialogs or activities
+ * @return An [OnTriggeredResult] telling the caller what to do next
+ */
+ fun onTriggered(expandable: Expandable?): OnTriggeredResult
+
+ /**
+ * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
+ * button on the lock-screen).
+ */
+ sealed class LockScreenState {
+
+ /** No affordance should show up. */
+ object Hidden : LockScreenState()
+
+ /** An affordance is visible. */
+ data class Visible(
+ /** An icon for the affordance. */
+ val icon: Icon,
+ /** The activation state of the affordance. */
+ val activationState: ActivationState = ActivationState.NotSupported,
+ ) : LockScreenState()
+ }
+
+ sealed class OnTriggeredResult {
+ /**
+ * Returning this as a result from the [onTriggered] method means that the implementation
+ * has taken care of the action, the system will do nothing.
+ */
+ object Handled : OnTriggeredResult()
+
+ /**
+ * Returning this as a result from the [onTriggered] method means that the implementation
+ * has _not_ taken care of the action and the system should start an activity using the
+ * given [Intent].
+ */
+ data class StartActivity(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ ) : OnTriggeredResult()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..d620b2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** QR code scanner quick affordance data source. */
+@SysUISingleton
+class QrCodeScannerKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val controller: QRCodeScannerController,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ override fun onQRCodeScannerPreferenceChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ // Registering does not push an initial update.
+ trySendWithFailureLogging(state(), "initial state", TAG)
+
+ awaitClose {
+ controller.unregisterQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ controller.removeCallback(callback)
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?,
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = controller.intent,
+ canShowWhileLocked = true,
+ )
+ }
+
+ private fun state(): KeyguardQuickAffordanceConfig.LockScreenState {
+ return if (controller.isEnabledForLockScreenButton) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_qr_code_scanner,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_qr_code_scanner_button,
+ ),
+ ),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..be57a32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Quick access wallet quick affordance data source. */
+@SysUISingleton
+class QuickAccessWalletKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val walletController: QuickAccessWalletController,
+ private val activityStarter: ActivityStarter,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = walletController.isWalletEnabled,
+ hasCard = response?.walletCards?.isNotEmpty() == true,
+ tileIcon = walletController.walletClient.tileIcon,
+ ),
+ TAG,
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+ trySendWithFailureLogging(
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
+ TAG,
+ )
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?,
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ walletController.startQuickAccessUiIntent(
+ activityStarter,
+ expandable?.activityLaunchController(),
+ /* hasCard= */ true,
+ )
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasCard: Boolean,
+ tileIcon: Drawable?,
+ ): KeyguardQuickAffordanceConfig.LockScreenState {
+ return if (isFeatureEnabled && hasCard && tileIcon != null) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Loaded(
+ drawable = tileIcon,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ ),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index e8532ec..ab25597 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -20,6 +20,7 @@
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.annotation.FloatRange
+import android.os.Trace
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -157,12 +158,36 @@
value: Float,
transitionState: TransitionState
) {
+ trace(info, transitionState)
+
if (transitionState == TransitionState.FINISHED) {
currentTransitionInfo = null
}
_transitions.value = TransitionStep(info.from, info.to, value, transitionState)
}
+ private fun trace(info: TransitionInfo, transitionState: TransitionState) {
+ if (
+ transitionState != TransitionState.STARTED &&
+ transitionState != TransitionState.FINISHED
+ ) {
+ return
+ }
+ val traceName =
+ "Transition: ${info.from} -> ${info.to} " +
+ if (info.animator == null) {
+ "(manual)"
+ } else {
+ ""
+ }
+ val traceCookie = traceName.hashCode()
+ if (transitionState == TransitionState.STARTED) {
+ Trace.beginAsyncSection(traceName, traceCookie)
+ } else if (transitionState == TransitionState.FINISHED) {
+ Trace.endAsyncSection(traceName, traceCookie)
+ }
+ }
+
companion object {
private const val TAG = "KeyguardTransitionRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index f663b0d..13d97aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -21,15 +21,14 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
@@ -63,25 +62,25 @@
}
/**
- * Notifies that a quick affordance has been clicked by the user.
+ * Notifies that a quick affordance has been "triggered" (clicked) by the user.
*
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
* the affordance that was clicked
* @param expandable An optional [Expandable] for the activity- or dialog-launch animation
*/
- fun onQuickAffordanceClicked(
- configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ fun onQuickAffordanceTriggered(
+ configKey: String,
expandable: Expandable?,
) {
- @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
- when (val result = config.onQuickAffordanceClicked(expandable)) {
- is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+ @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+ when (val result = config.onTriggered(expandable)) {
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
launchQuickAffordance(
intent = result.intent,
canShowWhileLocked = result.canShowWhileLocked,
expandable = expandable,
)
- is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit
}
}
@@ -95,16 +94,20 @@
// value and avoid subtle bugs where the downstream isn't receiving any values
// because one config implementation is not emitting an initial value. For example,
// see b/244296596.
- config.state.onStart { emit(KeyguardQuickAffordanceConfig.State.Hidden) }
+ config.lockScreenState.onStart {
+ emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
}
) { states ->
- val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+ val index =
+ states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
if (index != -1) {
- val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+ val visibleState =
+ states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index]::class,
+ configKey = configs[index].key,
icon = visibleState.icon,
- toggle = visibleState.toggle,
+ activationState = visibleState.activationState,
)
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 59bb22786..7409aec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -24,6 +24,8 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
/** Encapsulates business-logic related to the keyguard transitions. */
@SysUISingleton
@@ -34,4 +36,17 @@
) {
/** AOD->LOCKSCREEN transition information. */
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
+
+ /** LOCKSCREEN->AOD transition information. */
+ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
+
+ /**
+ * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
+ * Lockscreen (0f).
+ */
+ val dozeAmountTransition: Flow<TransitionStep> =
+ merge(
+ aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
+ lockscreenToAodTransition,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index e56b259..32560af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -18,9 +18,7 @@
package com.android.systemui.keyguard.domain.model
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
-import kotlin.reflect.KClass
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
/**
* Models a "quick affordance" in the keyguard bottom area (for example, a button on the
@@ -33,10 +31,10 @@
/** A affordance is visible. */
data class Visible(
/** Identifier for the affordance this is modeling. */
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
/** An icon for the affordance. */
val icon: Icon,
- /** The toggle state for the affordance. */
- val toggle: KeyguardQuickAffordanceToggleState,
+ /** The activation state of the affordance. */
+ val activationState: ActivationState,
) : KeyguardQuickAffordanceModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 95027d0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import android.content.Intent
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
-import kotlinx.coroutines.flow.Flow
-
-/** Defines interface that can act as data source for a single quick affordance model. */
-interface KeyguardQuickAffordanceConfig {
-
- val state: Flow<State>
-
- fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
-
- /**
- * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
- * button on the lock-screen).
- */
- sealed class State {
-
- /** No affordance should show up. */
- object Hidden : State()
-
- /** An affordance is visible. */
- data class Visible(
- /** An icon for the affordance. */
- val icon: Icon,
- /** The toggle state for the affordance. */
- val toggle: KeyguardQuickAffordanceToggleState =
- KeyguardQuickAffordanceToggleState.NotSupported,
- ) : State()
- }
-
- sealed class OnClickedResult {
- /**
- * Returning this as a result from the [onQuickAffordanceClicked] method means that the
- * implementation has taken care of the click, the system will do nothing.
- */
- object Handled : OnClickedResult()
-
- /**
- * Returning this as a result from the [onQuickAffordanceClicked] method means that the
- * implementation has _not_ taken care of the click and the system should start an activity
- * using the given [Intent].
- */
- data class StartActivity(
- val intent: Intent,
- val canShowWhileLocked: Boolean,
- ) : OnClickedResult()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
index 94024d4..b48acb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.quickaffordance
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import dagger.Binds
import dagger.Module
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index ad40ee7..8526ada 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -17,14 +17,17 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import javax.inject.Inject
-import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */
interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
fun getAll(position: KeyguardQuickAffordancePosition): List<T>
- fun get(configClass: KClass<out T>): T
+ fun get(key: String): T
}
class KeyguardQuickAffordanceRegistryImpl
@@ -46,8 +49,8 @@
qrCodeScanner,
),
)
- private val configByClass =
- configsByPosition.values.flatten().associateBy { config -> config::class }
+ private val configByKey =
+ configsByPosition.values.flatten().associateBy { config -> config.key }
override fun getAll(
position: KeyguardQuickAffordancePosition,
@@ -56,8 +59,8 @@
}
override fun get(
- configClass: KClass<out KeyguardQuickAffordanceConfig>
+ key: String,
): KeyguardQuickAffordanceConfig {
- return configByClass.getValue(configClass)
+ return configByKey.getValue(key)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 502a607..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.R
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** QR code scanner quick affordance data source. */
-@SysUISingleton
-class QrCodeScannerKeyguardQuickAffordanceConfig
-@Inject
-constructor(
- private val controller: QRCodeScannerController,
-) : KeyguardQuickAffordanceConfig {
-
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
- val callback =
- object : QRCodeScannerController.Callback {
- override fun onQRCodeScannerActivityChanged() {
- trySendWithFailureLogging(state(), TAG)
- }
- override fun onQRCodeScannerPreferenceChanged() {
- trySendWithFailureLogging(state(), TAG)
- }
- }
-
- controller.addCallback(callback)
- controller.registerQRCodeScannerChangeObservers(
- QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
- QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
- )
- // Registering does not push an initial update.
- trySendWithFailureLogging(state(), "initial state", TAG)
-
- awaitClose {
- controller.unregisterQRCodeScannerChangeObservers(
- QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
- QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
- )
- controller.removeCallback(callback)
- }
- }
-
- override fun onQuickAffordanceClicked(
- expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
- intent = controller.intent,
- canShowWhileLocked = true,
- )
- }
-
- private fun state(): KeyguardQuickAffordanceConfig.State {
- return if (controller.isEnabledForLockScreenButton) {
- KeyguardQuickAffordanceConfig.State.Visible(
- icon =
- Icon.Resource(
- res = R.drawable.ic_qr_code_scanner,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_qr_code_scanner_button,
- ),
- ),
- )
- } else {
- KeyguardQuickAffordanceConfig.State.Hidden
- }
- }
-
- companion object {
- private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index a24a0d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import android.graphics.drawable.Drawable
-import android.service.quickaccesswallet.GetWalletCardsError
-import android.service.quickaccesswallet.GetWalletCardsResponse
-import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.util.Log
-import com.android.systemui.R
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.wallet.controller.QuickAccessWalletController
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** Quick access wallet quick affordance data source. */
-@SysUISingleton
-class QuickAccessWalletKeyguardQuickAffordanceConfig
-@Inject
-constructor(
- private val walletController: QuickAccessWalletController,
- private val activityStarter: ActivityStarter,
-) : KeyguardQuickAffordanceConfig {
-
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
- val callback =
- object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
- trySendWithFailureLogging(
- state(
- isFeatureEnabled = walletController.isWalletEnabled,
- hasCard = response?.walletCards?.isNotEmpty() == true,
- tileIcon = walletController.walletClient.tileIcon,
- ),
- TAG,
- )
- }
-
- override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
- Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
- trySendWithFailureLogging(
- KeyguardQuickAffordanceConfig.State.Hidden,
- TAG,
- )
- }
- }
-
- walletController.setupWalletChangeObservers(
- callback,
- QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
- )
- walletController.updateWalletPreference()
- walletController.queryWalletCards(callback)
-
- awaitClose {
- walletController.unregisterWalletChangeObservers(
- QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
- )
- }
- }
-
- override fun onQuickAffordanceClicked(
- expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- walletController.startQuickAccessUiIntent(
- activityStarter,
- expandable?.activityLaunchController(),
- /* hasCard= */ true,
- )
- return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
- }
-
- private fun state(
- isFeatureEnabled: Boolean,
- hasCard: Boolean,
- tileIcon: Drawable?,
- ): KeyguardQuickAffordanceConfig.State {
- return if (isFeatureEnabled && hasCard && tileIcon != null) {
- KeyguardQuickAffordanceConfig.State.Visible(
- icon =
- Icon.Loaded(
- drawable = tileIcon,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
- ),
- )
- } else {
- KeyguardQuickAffordanceConfig.State.Hidden
- }
- }
-
- companion object {
- private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
index 55d38a4..a68d190 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
@@ -17,12 +17,12 @@
package com.android.systemui.keyguard.shared.quickaffordance
-/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */
-sealed class KeyguardQuickAffordanceToggleState {
- /** Toggling is not supported. */
- object NotSupported : KeyguardQuickAffordanceToggleState()
+/** Enumerates all possible activation states for a quick affordance on the lock-screen. */
+sealed class ActivationState {
+ /** Activation is not supported. */
+ object NotSupported : ActivationState()
/** The quick affordance is on. */
- object On : KeyguardQuickAffordanceToggleState()
+ object Active : ActivationState()
/** The quick affordance is off. */
- object Off : KeyguardQuickAffordanceToggleState()
+ object Inactive : ActivationState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index 581dafa3..a18b036 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.keyguard.shared.quickaffordance
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 535ca72..b6b2304 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -22,8 +22,8 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -118,13 +118,13 @@
animateReveal = animateReveal,
icon = icon,
onClicked = { parameters ->
- quickAffordanceInteractor.onQuickAffordanceClicked(
+ quickAffordanceInteractor.onQuickAffordanceTriggered(
configKey = parameters.configKey,
expandable = parameters.expandable,
)
},
isClickable = isClickable,
- isActivated = toggle is KeyguardQuickAffordanceToggleState.On,
+ isActivated = activationState is ActivationState.Active,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index bf598ba..44f48f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -18,12 +18,10 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
+ val configKey: String? = null,
val isVisible: Boolean = false,
/** Whether to animate the transition of the quick affordance from invisible to visible. */
val animateReveal: Boolean = false,
@@ -33,7 +31,7 @@
val isActivated: Boolean = false,
) {
data class OnClickedParameters(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
val expandable: Expandable?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 6b46d8f..cbb670e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -43,7 +43,8 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.NotifPanelEvents
+import com.android.systemui.shade.ShadeStateEvents
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -96,7 +97,7 @@
private val dreamOverlayStateController: DreamOverlayStateController,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- panelEventsEvents: NotifPanelEvents,
+ panelEventsEvents: ShadeStateEvents,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
) {
@@ -534,8 +535,8 @@
mediaHosts.forEach { it?.updateViewVisibility() }
}
- panelEventsEvents.registerListener(
- object : NotifPanelEvents.Listener {
+ panelEventsEvents.addShadeStateEventsListener(
+ object : ShadeStateEventsListener {
override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
skipQqsOnExpansion = isExpandImmediateEnabled
updateDesiredLocation()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 38c971e..120f7d6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -43,6 +43,21 @@
)
}
+ /**
+ * Logs an error in trying to update to [displayState].
+ *
+ * [displayState] is either a [android.app.StatusBarManager.MediaTransferSenderState] or
+ * a [android.app.StatusBarManager.MediaTransferReceiverState].
+ */
+ fun logStateChangeError(displayState: Int) {
+ buffer.log(
+ tag,
+ LogLevel.ERROR,
+ { int1 = displayState },
+ { "Cannot display state=$int1; aborting" }
+ )
+ }
+
/** Logs that we couldn't find information for [packageName]. */
fun logPackageNotFound(packageName: String) {
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 0a60437..769494a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -27,10 +27,11 @@
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
companion object {
- // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
- // UpdateMediaTapToTransferReceiverDisplayTest
- const val WINDOW_TITLE = "Media Transfer Chip View"
- const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
+ const val WINDOW_TITLE_SENDER = "Media Transfer Chip View (Sender)"
+ const val WINDOW_TITLE_RECEIVER = "Media Transfer Chip View (Receiver)"
+
+ const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER"
+ const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
/**
* Returns the information needed to display the icon in [Icon] form.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 089625c..7dd9fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -25,7 +25,6 @@
import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
-import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
@@ -41,7 +40,6 @@
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -79,8 +77,6 @@
configurationController,
powerManager,
R.layout.media_ttt_chip_receiver,
- MediaTttUtils.WINDOW_TITLE,
- MediaTttUtils.WAKE_REASON,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -116,7 +112,7 @@
logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
- Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
+ logger.logStateChangeError(displayState)
return
}
uiEventLogger.logReceiverStateChange(chipState)
@@ -232,9 +228,7 @@
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
- val appNameOverride: CharSequence?
-) : TemporaryViewInfo {
- override fun getTimeoutMs() = DEFAULT_TIMEOUT_MILLIS
-}
-
-private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
+ val appNameOverride: CharSequence?,
+ override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
+ override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
+) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 6e596ee..af7317c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -43,7 +43,7 @@
@StringRes val stringResId: Int?,
val transferStatus: TransferStatus,
val endItem: SenderEndItem?,
- val timeout: Long = DEFAULT_TIMEOUT_MILLIS
+ val timeout: Int = DEFAULT_TIMEOUT_MILLIS,
) {
/**
* A state representing that the two devices are close but not close enough to *start* a cast to
@@ -223,6 +223,6 @@
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000
private const val TAG = "ChipStateSender"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index edf759d..d1ea2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -19,7 +19,6 @@
import android.app.StatusBarManager
import android.content.Context
import android.media.MediaRoute2Info
-import android.util.Log
import android.view.View
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
@@ -34,7 +33,6 @@
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
-import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG
import javax.inject.Inject
/**
@@ -86,7 +84,7 @@
logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
- Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
+ logger.logStateChangeError(displayState)
return
}
uiEventLogger.logSenderStateChange(chipState)
@@ -161,6 +159,9 @@
}
},
vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+ windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
+ wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
+ timeoutMs = chipStateSender.timeout,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
new file mode 100644
index 0000000..d247f24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.os.UserManager
+import android.view.KeyEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.Optional
+import javax.inject.Inject
+
+/**
+ * Entry point for creating and managing note.
+ *
+ * The controller decides how a note is launched based in the device state: locked or unlocked.
+ *
+ * Currently, we only support a single task per time.
+ */
+@SysUISingleton
+internal class NoteTaskController
+@Inject
+constructor(
+ private val context: Context,
+ private val intentResolver: NoteTaskIntentResolver,
+ private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val optionalKeyguardManager: Optional<KeyguardManager>,
+ private val optionalUserManager: Optional<UserManager>,
+ @NoteTaskEnabledKey private val isEnabled: Boolean,
+) {
+
+ fun handleSystemKey(keyCode: Int) {
+ if (!isEnabled) return
+
+ if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+ showNoteTask()
+ }
+ }
+
+ private fun showNoteTask() {
+ val floatingTasks = optionalFloatingTasks.getOrNull() ?: return
+ val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
+ val userManager = optionalUserManager.getOrNull() ?: return
+ val intent = intentResolver.resolveIntent() ?: return
+
+ // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
+ if (!userManager.isUserUnlocked) return
+
+ if (keyguardManager.isKeyguardLocked) {
+ context.startActivity(intent)
+ } else {
+ // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
+ floatingTasks.showOrSetStashed(intent)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
index 581dafa3..e0bf1da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.notetask
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
-}
+import javax.inject.Qualifier
+
+/** Key associated with a [Boolean] flag that enables or disables the note task feature. */
+@Qualifier internal annotation class NoteTaskEnabledKey
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
new file mode 100644
index 0000000..d84717d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import com.android.systemui.statusbar.CommandQueue
+import com.android.wm.shell.floating.FloatingTasks
+import dagger.Lazy
+import java.util.Optional
+import javax.inject.Inject
+
+/** Class responsible to "glue" all note task dependencies. */
+internal class NoteTaskInitializer
+@Inject
+constructor(
+ private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val lazyNoteTaskController: Lazy<NoteTaskController>,
+ private val commandQueue: CommandQueue,
+ @NoteTaskEnabledKey private val isEnabled: Boolean,
+) {
+
+ private val callbacks =
+ object : CommandQueue.Callbacks {
+ override fun handleSystemKey(keyCode: Int) {
+ lazyNoteTaskController.get().handleSystemKey(keyCode)
+ }
+ }
+
+ fun initialize() {
+ if (isEnabled && optionalFloatingTasks.isPresent) {
+ commandQueue.addCallback(callbacks)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
new file mode 100644
index 0000000..98d6991
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import javax.inject.Inject
+
+/**
+ * Class responsible to query all apps and find one that can handle the [NOTES_ACTION]. If found, an
+ * [Intent] ready for be launched will be returned. Otherwise, returns null.
+ *
+ * TODO(b/248274123): should be revisited once the notes role is implemented.
+ */
+internal class NoteTaskIntentResolver
+@Inject
+constructor(
+ private val packageManager: PackageManager,
+) {
+
+ fun resolveIntent(): Intent? {
+ val intent = Intent(NOTES_ACTION)
+ val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
+ val infoList = packageManager.queryIntentActivities(intent, flags)
+
+ for (info in infoList) {
+ val packageName = info.serviceInfo.applicationInfo.packageName ?: continue
+ val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
+
+ return Intent(NOTES_ACTION)
+ .setComponent(ComponentName(packageName, activityName))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ return null
+ }
+
+ private fun resolveActivityNameForNotesAction(packageName: String): String? {
+ val intent = Intent(NOTES_ACTION).setPackage(packageName)
+ val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
+ val resolveInfo = packageManager.resolveActivity(intent, flags)
+
+ val activityInfo = resolveInfo?.activityInfo ?: return null
+ if (activityInfo.name.isNullOrBlank()) return null
+ if (!activityInfo.exported) return null
+ if (!activityInfo.enabled) return null
+ if (!activityInfo.showWhenLocked) return null
+ if (!activityInfo.turnScreenOn) return null
+
+ return activityInfo.name
+ }
+
+ companion object {
+ // TODO(b/254606432): Use Intent.ACTION_NOTES and Intent.ACTION_NOTES_LOCKED instead.
+ const val NOTES_ACTION = "android.intent.action.NOTES"
+ }
+}
+
+private val ActivityInfo.showWhenLocked: Boolean
+ get() = flags and ActivityInfo.FLAG_SHOW_WHEN_LOCKED != 0
+
+private val ActivityInfo.turnScreenOn: Boolean
+ get() = flags and ActivityInfo.FLAG_TURN_SCREEN_ON != 0
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
new file mode 100644
index 0000000..035396a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.os.UserManager
+import androidx.core.content.getSystemService
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import dagger.Module
+import dagger.Provides
+import java.util.*
+
+/** Compose all dependencies required by Note Task feature. */
+@Module
+internal class NoteTaskModule {
+
+ @[Provides NoteTaskEnabledKey]
+ fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
+ return featureFlags.isEnabled(Flags.NOTE_TASKS)
+ }
+
+ @Provides
+ fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
+
+ @Provides
+ fun provideOptionalUserManager(context: Context): Optional<UserManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index b6b657e..57a00c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -204,6 +204,15 @@
Trace.endSection();
}
+ @Override
+ public void onUserListItemClicked(@NonNull UserRecord record,
+ @Nullable UserSwitchDialogController.DialogShower dialogShower) {
+ if (dialogShower != null) {
+ mDialogShower.dismiss();
+ }
+ super.onUserListItemClicked(record, dialogShower);
+ }
+
public void linkToViewGroup(ViewGroup viewGroup) {
PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 231e415..d524a35 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -20,6 +20,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
@@ -634,6 +635,11 @@
return true;
}
});
+
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mScreenshotView.badgeScreenshot(
+ mContext.getPackageManager().getUserBadgeForDensity(owner, 0));
+ }
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mScreenshotView);
@@ -1038,7 +1044,7 @@
private boolean isUserSetupComplete(UserHandle owner) {
return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
- .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 1b9cdd4..27331ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -74,7 +74,6 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -122,15 +121,9 @@
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
- private static final float ROUNDED_CORNER_RADIUS = .25f;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
- private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
-
private final Resources mResources;
private final Interpolator mFastOutSlowIn;
private final DisplayMetrics mDisplayMetrics;
@@ -145,6 +138,7 @@
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
private ImageView mScreenshotPreview;
+ private ImageView mScreenshotBadge;
private View mScreenshotPreviewBorder;
private ImageView mScrollablePreview;
private ImageView mScreenshotFlash;
@@ -355,6 +349,7 @@
mScreenshotPreviewBorder = requireNonNull(
findViewById(R.id.screenshot_preview_border));
mScreenshotPreview.setClipToOutline(true);
+ mScreenshotBadge = requireNonNull(findViewById(R.id.screenshot_badge));
mActionsContainerBackground = requireNonNull(findViewById(
R.id.actions_container_background));
@@ -595,8 +590,11 @@
ValueAnimator borderFadeIn = ValueAnimator.ofFloat(0, 1);
borderFadeIn.setDuration(100);
- borderFadeIn.addUpdateListener((animation) ->
- mScreenshotPreviewBorder.setAlpha(animation.getAnimatedFraction()));
+ borderFadeIn.addUpdateListener((animation) -> {
+ float borderAlpha = animation.getAnimatedFraction();
+ mScreenshotPreviewBorder.setAlpha(borderAlpha);
+ mScreenshotBadge.setAlpha(borderAlpha);
+ });
if (showFlash) {
dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
@@ -763,6 +761,11 @@
return animator;
}
+ void badgeScreenshot(Drawable badge) {
+ mScreenshotBadge.setImageDrawable(badge);
+ mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
+ }
+
void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
@@ -1027,6 +1030,9 @@
mScreenshotPreview.setVisibility(View.INVISIBLE);
mScreenshotPreview.setAlpha(1f);
mScreenshotPreviewBorder.setAlpha(0);
+ mScreenshotBadge.setAlpha(0f);
+ mScreenshotBadge.setVisibility(View.GONE);
+ mScreenshotBadge.setImageDrawable(null);
mPendingSharedTransition = false;
mActionsContainerBackground.setVisibility(View.GONE);
mActionsContainer.setVisibility(View.GONE);
@@ -1082,6 +1088,7 @@
mActionsContainerBackground.setAlpha(alpha);
mActionsContainer.setAlpha(alpha);
mScreenshotPreviewBorder.setAlpha(alpha);
+ mScreenshotBadge.setAlpha(alpha);
});
alphaAnim.setDuration(600);
return alphaAnim;
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index bbba007..b36f0d7 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -33,13 +33,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
/**
* Drawable used on SysUI scrims.
*/
public class ScrimDrawable extends Drawable {
private static final String TAG = "ScrimDrawable";
- private static final long COLOR_ANIMATION_DURATION = 2000;
private final Paint mPaint;
private int mAlpha = 255;
@@ -76,7 +76,7 @@
final int mainFrom = mMainColor;
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- anim.setDuration(COLOR_ANIMATION_DURATION);
+ anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
anim.addUpdateListener(animation -> {
float ratio = (float) animation.getAnimatedValue();
mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ddb57f7..40b58b9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -135,7 +135,6 @@
import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeLog;
@@ -231,7 +230,6 @@
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.LargeScreenUtils;
-import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.Utils;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -372,7 +370,6 @@
private final TapAgainViewController mTapAgainViewController;
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
private final RecordingController mRecordingController;
- private final PanelEventsEmitter mPanelEventsEmitter;
private final boolean mVibrateOnOpening;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -880,7 +877,6 @@
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationListContainer notificationListContainer,
- PanelEventsEmitter panelEventsEmitter,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
@@ -993,7 +989,6 @@
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
- mPanelEventsEmitter = panelEventsEmitter;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
mQs.animateHeaderSlidingOut();
@@ -1948,7 +1943,7 @@
private void setQsExpandImmediate(boolean expandImmediate) {
if (expandImmediate != mQsExpandImmediate) {
mQsExpandImmediate = expandImmediate;
- mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
}
}
@@ -2674,8 +2669,8 @@
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
- if (height == 0) {
- mCentralSurfaces.requestFaceAuth(false, FaceAuthApiRequestReason.QS_EXPANDED);
+ if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+ mUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
}
}
@@ -3889,7 +3884,7 @@
boolean wasRunning = mIsLaunchAnimationRunning;
mIsLaunchAnimationRunning = running;
if (wasRunning != mIsLaunchAnimationRunning) {
- mPanelEventsEmitter.notifyLaunchingActivityChanged(running);
+ mShadeExpansionStateManager.notifyLaunchingActivityChanged(running);
}
}
@@ -3898,7 +3893,7 @@
boolean wasClosing = isClosing();
mClosing = isClosing;
if (wasClosing != isClosing) {
- mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
+ mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
}
mAmbientState.setIsClosing(isClosing);
}
@@ -3933,7 +3928,7 @@
mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
// Try triggering face auth, this "might" run. Check
// KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
- boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
if (didFaceAuthRun) {
@@ -5921,44 +5916,6 @@
}
}
- @SysUISingleton
- static class PanelEventsEmitter implements NotifPanelEvents {
-
- private final ListenerSet<Listener> mListeners = new ListenerSet<>();
-
- @Inject
- PanelEventsEmitter() {
- }
-
- @Override
- public void registerListener(@androidx.annotation.NonNull @NonNull Listener listener) {
- mListeners.addIfAbsent(listener);
- }
-
- @Override
- public void unregisterListener(@androidx.annotation.NonNull @NonNull Listener listener) {
- mListeners.remove(listener);
- }
-
- private void notifyLaunchingActivityChanged(boolean isLaunchingActivity) {
- for (Listener cb : mListeners) {
- cb.onLaunchingActivityChanged(isLaunchingActivity);
- }
- }
-
- private void notifyPanelCollapsingChanged(boolean isCollapsing) {
- for (NotifPanelEvents.Listener cb : mListeners) {
- cb.onPanelCollapsingChanged(isCollapsing);
- }
- }
-
- private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
- for (NotifPanelEvents.Listener cb : mListeners) {
- cb.onExpandImmediateChanged(expandImmediateEnabled);
- }
- }
- }
-
/** Handles MotionEvents for the Shade. */
public final class TouchHandler implements View.OnTouchListener {
private long mLastTouchDownTime = -1L;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
index 6772384..959c339 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
@@ -21,10 +21,9 @@
import dagger.Binds;
import dagger.Module;
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
+/** Provides a {@link ShadeStateEvents} in {@link SysUISingleton} scope. */
@Module
-public abstract class NotifPanelEventsModule {
+public abstract class ShadeEventsModule {
@Binds
- abstract NotifPanelEvents bindPanelEvents(
- NotificationPanelViewController.PanelEventsEmitter impl);
+ abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 7bba74a..667392c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -20,6 +20,7 @@
import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
import com.android.systemui.util.Compile
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
@@ -30,11 +31,12 @@
* TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
*/
@SysUISingleton
-class ShadeExpansionStateManager @Inject constructor() {
+class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
+ private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
@@ -79,6 +81,14 @@
stateListeners.remove(listener)
}
+ override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
+ shadeStateEventsListeners.addIfAbsent(listener)
+ }
+
+ override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) {
+ shadeStateEventsListeners.remove(listener)
+ }
+
/** Returns true if the panel is currently closed and false otherwise. */
fun isClosed(): Boolean = state == STATE_CLOSED
@@ -162,6 +172,24 @@
stateListeners.forEach { it.onPanelStateChanged(state) }
}
+ fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onLaunchingActivityChanged(isLaunchingActivity)
+ }
+ }
+
+ fun notifyPanelCollapsingChanged(isCollapsing: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onPanelCollapsingChanged(isCollapsing)
+ }
+ }
+
+ fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onExpandImmediateChanged(expandImmediateEnabled)
+ }
+ }
+
private fun debugLog(msg: String) {
if (!DEBUG) return
Log.v(TAG, msg)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
index 4558061..56bb1a6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
@@ -16,27 +16,25 @@
package com.android.systemui.shade
-/** Provides certain notification panel events. */
-interface NotifPanelEvents {
+/** Provides certain notification panel events. */
+interface ShadeStateEvents {
- /** Registers callbacks to be invoked when notification panel events occur. */
- fun registerListener(listener: Listener)
+ /** Registers callbacks to be invoked when notification panel events occur. */
+ fun addShadeStateEventsListener(listener: ShadeStateEventsListener)
- /** Unregisters callbacks previously registered via [registerListener] */
- fun unregisterListener(listener: Listener)
+ /** Unregisters callbacks previously registered via [addShadeStateEventsListener] */
+ fun removeShadeStateEventsListener(listener: ShadeStateEventsListener)
/** Callbacks for certain notification panel events. */
- interface Listener {
+ interface ShadeStateEventsListener {
/** Invoked when the notification panel starts or stops collapsing. */
- @JvmDefault
- fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
+ @JvmDefault fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
/**
* Invoked when the notification panel starts or stops launching an [android.app.Activity].
*/
- @JvmDefault
- fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+ @JvmDefault fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
/**
* Invoked when the "expand immediate" attribute changes.
@@ -47,7 +45,6 @@
* Another example is when full QS is showing, and we swipe up from the bottom. Instead of
* going to QQS, the panel fully collapses.
*/
- @JvmDefault
- fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
+ @JvmDefault fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index d3bc257..3002a68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -28,7 +28,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -55,12 +55,12 @@
// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator, Dumpable,
- NotifPanelEvents.Listener {
+ ShadeStateEvents.ShadeStateEventsListener {
public static final String TAG = "VisualStability";
public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private final DelayableExecutor mDelayableExecutor;
private final HeadsUpManager mHeadsUpManager;
- private final NotifPanelEvents mNotifPanelEvents;
+ private final ShadeStateEvents mShadeStateEvents;
private final StatusBarStateController mStatusBarStateController;
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -92,7 +92,7 @@
DelayableExecutor delayableExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
- NotifPanelEvents notifPanelEvents,
+ ShadeStateEvents shadeStateEvents,
StatusBarStateController statusBarStateController,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle) {
@@ -101,7 +101,7 @@
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mDelayableExecutor = delayableExecutor;
- mNotifPanelEvents = notifPanelEvents;
+ mShadeStateEvents = shadeStateEvents;
dumpManager.registerDumpable(this);
}
@@ -114,7 +114,7 @@
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
mPulsing = mStatusBarStateController.isPulsing();
- mNotifPanelEvents.registerListener(this);
+ mShadeStateEvents.addShadeStateEventsListener(this);
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index da4cced..ff63891 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -32,8 +32,8 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.NotifPanelEventsModule;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeEventsModule;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
@@ -93,7 +93,7 @@
@Module(includes = {
CoordinatorsModule.class,
KeyguardNotificationVisibilityProviderModule.class,
- NotifPanelEventsModule.class,
+ ShadeEventsModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 8de0365..277ad8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1374,13 +1374,8 @@
if (bubbleButton == null || actionContainer == null) {
return;
}
- boolean isPersonWithShortcut =
- mPeopleIdentifier.getPeopleNotificationType(entry)
- >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
- boolean showButton = BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
- && isPersonWithShortcut
- && entry.getBubbleMetadata() != null;
- if (showButton) {
+
+ if (shouldShowBubbleButton(entry)) {
// explicitly resolve drawable resource using SystemUI's theme
Drawable d = mContext.getDrawable(entry.isBubble()
? R.drawable.bubble_ic_stop_bubble
@@ -1410,6 +1405,16 @@
}
}
+ @VisibleForTesting
+ boolean shouldShowBubbleButton(NotificationEntry entry) {
+ boolean isPersonWithShortcut =
+ mPeopleIdentifier.getPeopleNotificationType(entry)
+ >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
+ return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
+ && isPersonWithShortcut
+ && entry.getBubbleMetadata() != null;
+ }
+
private void applySnoozeAction(View layout) {
if (layout == null || mContainingNotification == null) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 169c907..75b444f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -41,7 +41,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.RemoteTransitionAdapter;
@@ -230,13 +229,6 @@
boolean isShadeDisabled();
- /**
- * Request face auth to initiated
- * @param userInitiatedRequest Whether this was a user initiated request
- * @param reason Reason why face auth was triggered.
- */
- void requestFaceAuth(boolean userInitiatedRequest, @FaceAuthApiRequestReason String reason);
-
@Override
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
int flags);
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 db8aac95..409cad0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -123,7 +123,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
@@ -1632,18 +1631,6 @@
return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
}
- /**
- * Asks {@link KeyguardUpdateMonitor} to run face auth.
- */
- @Override
- public void requestFaceAuth(boolean userInitiatedRequest,
- @FaceAuthApiRequestReason String reason) {
- if (!mKeyguardStateController.canDismissLockScreen()) {
- mKeyguardUpdateMonitor.requestFaceAuth(
- userInitiatedRequest, reason);
- }
- }
-
private void updateReportRejectedTouchVisibility() {
if (mReportRejectedTouch == null) {
return;
@@ -2980,7 +2967,10 @@
// * When phone is unlocked: we still don't want to execute hiding of the keyguard
// as the animation could prepare 'fake AOD' interface (without actually
// transitioning to keyguard state) and this might reset the view states
- if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
+ if (!mScreenOffAnimationController.isKeyguardHideDelayed()
+ // If we're animating occluded, there's an activity launching over the keyguard
+ // UI. Wait to hide it until after the animation concludes.
+ && !mKeyguardViewMediator.isOccludeAnimationPlaying()) {
return hideKeyguardImpl(forceStateChange);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 5e26cf0..4550cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -73,7 +73,6 @@
isListening = false
updateListeningState()
keyguardUpdateMonitor.requestFaceAuth(
- true,
FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
)
keyguardUpdateMonitor.requestActiveUnlock(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 9767103..c189ace 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -649,37 +649,6 @@
return mNumDots > 0;
}
- /**
- * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
- * extra padding will have to be accounted for
- *
- * This method has no meaning for non-static containers
- */
- public boolean hasPartialOverflow() {
- return mNumDots > 0 && mNumDots < MAX_DOTS;
- }
-
- /**
- * Get padding that can account for extra dots up to the max. The only valid values for
- * this method are for 1 or 2 dots.
- * @return only extraDotPadding or extraDotPadding * 2
- */
- public int getPartialOverflowExtraPadding() {
- if (!hasPartialOverflow()) {
- return 0;
- }
-
- int partialOverflowAmount = (MAX_DOTS - mNumDots) * (mStaticDotDiameter + mDotPadding);
-
- int adjustedWidth = getFinalTranslationX() + partialOverflowAmount;
- // In case we actually give too much padding...
- if (adjustedWidth > getWidth()) {
- partialOverflowAmount = getWidth() - getFinalTranslationX();
- }
-
- return partialOverflowAmount;
- }
-
// Give some extra room for btw notifications if we can
public int getNoOverflowExtraPadding() {
if (mNumDots != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8490768..cf3a48c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,6 +53,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -204,6 +205,7 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardViewMediator mKeyguardViewMediator;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -273,7 +275,8 @@
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ KeyguardViewMediator keyguardViewMediator) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -312,6 +315,8 @@
}
});
mColors = new GradientColors();
+
+ mKeyguardViewMediator = keyguardViewMediator;
}
/**
@@ -807,6 +812,13 @@
mBehindTint,
interpolatedFraction);
}
+
+ // If we're unlocked but still playing the occlude animation, remain at the keyguard
+ // alpha temporarily.
+ if (mKeyguardViewMediator.isOccludeAnimationPlaying()
+ || mState.mLaunchingAffordanceWithPreview) {
+ mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
+ }
} else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index a0415f2..6cd8c78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -22,8 +22,6 @@
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
@@ -42,7 +40,6 @@
@Inject
internal constructor(
private val centralSurfaces: CentralSurfaces,
- private val featureFlags: FeatureFlags,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
private val statusBarStateController: SysuiStatusBarStateController,
private val lightBarController: LightBarController,
@@ -127,15 +124,11 @@
}
private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) =
- isLetterboxAppearanceFlagEnabled() && letterboxDetails.isNotEmpty()
-
- private fun isLetterboxAppearanceFlagEnabled() =
- featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)
+ letterboxDetails.isNotEmpty()
private fun dump(printWriter: PrintWriter, strings: Array<String>) {
printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams")
printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance")
- printWriter.println("letterbox appearance flag: ${isLetterboxAppearanceFlagEnabled()}")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2aaa085..fcd1b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -18,10 +18,14 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -41,10 +45,16 @@
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
- abstract fun mobileSubscriptionRepository(
- impl: MobileSubscriptionRepositoryImpl
- ): MobileSubscriptionRepository
+ abstract fun mobileConnectionsRepository(
+ impl: MobileConnectionsRepositoryImpl
+ ): MobileConnectionsRepository
@Binds
abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+
+ @Binds
+ abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+
+ @Binds
+ abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
index 46ccf32c..eaba0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -27,6 +27,7 @@
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
/**
* Data class containing all of the relevant information for a particular line of service, known as
@@ -57,6 +58,11 @@
/** From [CarrierNetworkListener.onCarrierNetworkChange] */
val carrierNetworkChangeActive: Boolean? = null,
- /** From [DisplayInfoListener.onDisplayInfoChanged] */
- val displayInfo: TelephonyDisplayInfo? = null
+ /**
+ * From [DisplayInfoListener.onDisplayInfoChanged].
+ *
+ * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+ * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
+ */
+ val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
new file mode 100644
index 0000000..f385806
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.telephony.Annotation.NetworkType
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+
+/**
+ * A SysUI type to represent the [NetworkType] that we pull out of [TelephonyDisplayInfo]. Depending
+ * on whether or not the display info contains an override type, we may have to call different
+ * methods on [MobileMappingsProxy] to generate an icon lookup key.
+ */
+sealed interface ResolvedNetworkType {
+ @NetworkType val type: Int
+}
+
+data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
+
+data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
new file mode 100644
index 0000000..45284cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import java.lang.IllegalStateException
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
+ * repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
+ * repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
+ *
+ * There should only ever be one [MobileConnectionRepository] per subscription, since
+ * [TelephonyManager] limits the number of callbacks that can be registered per process.
+ *
+ * This repository should have all of the relevant information for a single line of service, which
+ * eventually becomes a single icon in the status bar.
+ */
+interface MobileConnectionRepository {
+ /**
+ * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
+ * listener + model.
+ */
+ val subscriptionModelFlow: Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileConnectionRepositoryImpl(
+ private val subId: Int,
+ telephonyManager: TelephonyManager,
+ bgDispatcher: CoroutineDispatcher,
+ logger: ConnectivityPipelineLogger,
+ scope: CoroutineScope,
+) : MobileConnectionRepository {
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ throw IllegalStateException(
+ "TelephonyManager should be created with subId($subId). " +
+ "Found ${telephonyManager.subscriptionId} instead."
+ )
+ }
+ }
+
+ override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
+ var state = MobileSubscriptionModel()
+ conflatedCallbackFlow {
+ // TODO (b/240569788): log all of these into the connectivity logger
+ val callback =
+ object :
+ TelephonyCallback(),
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener,
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DataActivityListener,
+ TelephonyCallback.CarrierNetworkListener,
+ TelephonyCallback.DisplayInfoListener {
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ trySend(state)
+ }
+
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ val cdmaLevel =
+ signalStrength
+ .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+ .let { strengths ->
+ if (!strengths.isEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+
+ val primaryLevel = signalStrength.level
+
+ state =
+ state.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ trySend(state)
+ }
+
+ override fun onDataConnectionStateChanged(
+ dataState: Int,
+ networkType: Int
+ ) {
+ state = state.copy(dataConnectionState = dataState)
+ trySend(state)
+ }
+
+ override fun onDataActivity(direction: Int) {
+ state = state.copy(dataActivityDirection = direction)
+ trySend(state)
+ }
+
+ override fun onCarrierNetworkChange(active: Boolean) {
+ state = state.copy(carrierNetworkChangeActive = active)
+ trySend(state)
+ }
+
+ override fun onDisplayInfoChanged(
+ telephonyDisplayInfo: TelephonyDisplayInfo
+ ) {
+ val networkType =
+ if (
+ telephonyDisplayInfo.overrideNetworkType ==
+ OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(telephonyDisplayInfo.networkType)
+ } else {
+ OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
+ }
+ state = state.copy(resolvedNetworkType = networkType)
+ trySend(state)
+ }
+ }
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ }
+
+ class Factory
+ @Inject
+ constructor(
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ ) {
+ fun build(subId: Int): MobileConnectionRepository {
+ return MobileConnectionRepositoryImpl(
+ subId,
+ telephonyManager.createForSubscriptionId(subId),
+ bgDispatcher,
+ logger,
+ scope,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
new file mode 100644
index 0000000..0e2428a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.content.Context
+import android.content.IntentFilter
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileConnectionsRepository {
+ /** Observable list of current mobile subscriptions */
+ val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+ /** Observable for the subscriptionId of the current mobile data connection */
+ val activeMobileDataSubscriptionId: Flow<Int>
+
+ /** Observable for [MobileMappings.Config] tracking the defaults */
+ val defaultDataSubRatConfig: StateFlow<Config>
+
+ /** Get or create a repository for the line of service for the given subscription ID */
+ fun getRepoForSubId(subId: Int): MobileConnectionRepository
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileConnectionsRepositoryImpl
+@Inject
+constructor(
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val context: Context,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
+) : MobileConnectionsRepository {
+ private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+ * info object, but for now we keep track of the infos themselves.
+ */
+ override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(Unit)
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ bgDispatcher.asExecutor(),
+ callback,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .mapLatest { fetchSubscriptionsList() }
+ .onEach { infos -> dropUnusedReposFromCache(infos) }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+ /** StateFlow that keeps track of the current active mobile data subscription */
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ trySend(subId)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+
+ private val defaultDataSubChangedEvent =
+ broadcastDispatcher.broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ )
+
+ private val carrierConfigChangedEvent =
+ broadcastDispatcher.broadcastFlow(
+ IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+ )
+
+ /**
+ * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+ * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+ * config, so this will apply to every icon that we care about.
+ *
+ * Relevant bits in the config are things like
+ * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+ *
+ * This flow will produce whenever the default data subscription or the carrier config changes.
+ */
+ override val defaultDataSubRatConfig: StateFlow<Config> =
+ combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
+ Config.readConfig(context)
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = Config.readConfig(context)
+ )
+
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+ if (!isValidSubId(subId)) {
+ throw IllegalArgumentException(
+ "subscriptionId $subId is not in the list of valid subscriptions"
+ )
+ }
+
+ return subIdRepositoryCache[subId]
+ ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+ }
+
+ private fun isValidSubId(subId: Int): Boolean {
+ subscriptionsFlow.value.forEach {
+ if (it.subscriptionId == subId) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
+
+ private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
+ return mobileConnectionRepositoryFactory.build(subId)
+ }
+
+ private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
+ // Remove any connection repository from the cache that isn't in the new set of IDs. They
+ // will get garbage collected once their subscribers go away
+ val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
+
+ subIdRepositoryCache.keys.forEach {
+ if (!currentValidSubscriptionIds.contains(it)) {
+ subIdRepositoryCache.remove(it)
+ }
+ }
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
deleted file mode 100644
index 36de2a2..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import android.telephony.CellSignalStrength
-import android.telephony.CellSignalStrengthCdma
-import android.telephony.ServiceState
-import android.telephony.SignalStrength
-import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
-import android.telephony.TelephonyCallback
-import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
-
-/**
- * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
- * on various policy
- */
-interface MobileSubscriptionRepository {
- /** Observable list of current mobile subscriptions */
- val subscriptionsFlow: Flow<List<SubscriptionInfo>>
-
- /** Observable for the subscriptionId of the current mobile data connection */
- val activeMobileDataSubscriptionId: Flow<Int>
-
- /** Get or create an observable for the given subscription ID */
- fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
-}
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MobileSubscriptionRepositoryImpl
-@Inject
-constructor(
- private val subscriptionManager: SubscriptionManager,
- private val telephonyManager: TelephonyManager,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
-) : MobileSubscriptionRepository {
- private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
-
- /**
- * State flow that emits the set of mobile data subscriptions, each represented by its own
- * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
- * info object, but for now we keep track of the infos themselves.
- */
- override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
- conflatedCallbackFlow {
- val callback =
- object : SubscriptionManager.OnSubscriptionsChangedListener() {
- override fun onSubscriptionsChanged() {
- trySend(Unit)
- }
- }
-
- subscriptionManager.addOnSubscriptionsChangedListener(
- bgDispatcher.asExecutor(),
- callback,
- )
-
- awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
- }
- .mapLatest { fetchSubscriptionsList() }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
-
- /** StateFlow that keeps track of the current active mobile data subscription */
- override val activeMobileDataSubscriptionId: StateFlow<Int> =
- conflatedCallbackFlow {
- val callback =
- object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
- override fun onActiveDataSubscriptionIdChanged(subId: Int) {
- trySend(subId)
- }
- }
-
- telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
- awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
- }
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- SubscriptionManager.INVALID_SUBSCRIPTION_ID
- )
-
- /**
- * Each mobile subscription needs its own flow, which comes from registering listeners on the
- * system. Use this method to create those flows and cache them for reuse
- */
- override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
- return subIdFlowCache[subId]
- ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
- }
-
- @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
-
- private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
- var state = MobileSubscriptionModel()
- conflatedCallbackFlow {
- val phony = telephonyManager.createForSubscriptionId(subId)
- // TODO (b/240569788): log all of these into the connectivity logger
- val callback =
- object :
- TelephonyCallback(),
- ServiceStateListener,
- SignalStrengthsListener,
- DataConnectionStateListener,
- DataActivityListener,
- CarrierNetworkListener,
- DisplayInfoListener {
- override fun onServiceStateChanged(serviceState: ServiceState) {
- state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
- trySend(state)
- }
- override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
- val cdmaLevel =
- signalStrength
- .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
- .let { strengths ->
- if (!strengths.isEmpty()) {
- strengths[0].level
- } else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- }
- }
-
- val primaryLevel = signalStrength.level
-
- state =
- state.copy(
- cdmaLevel = cdmaLevel,
- primaryLevel = primaryLevel,
- isGsm = signalStrength.isGsm,
- )
- trySend(state)
- }
- override fun onDataConnectionStateChanged(
- dataState: Int,
- networkType: Int
- ) {
- state = state.copy(dataConnectionState = dataState)
- trySend(state)
- }
- override fun onDataActivity(direction: Int) {
- state = state.copy(dataActivityDirection = direction)
- trySend(state)
- }
- override fun onCarrierNetworkChange(active: Boolean) {
- state = state.copy(carrierNetworkChangeActive = active)
- trySend(state)
- }
- override fun onDisplayInfoChanged(
- telephonyDisplayInfo: TelephonyDisplayInfo
- ) {
- state = state.copy(displayInfo = telephonyDisplayInfo)
- trySend(state)
- }
- }
- phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
- awaitClose {
- phony.unregisterTelephonyCallback(callback)
- // Release the cached flow
- subIdFlowCache.remove(subId)
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), state)
- }
-
- private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
- withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 40fe0f3..15f4acc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -17,32 +17,58 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
-import com.android.settingslib.SignalIcon
-import com.android.settingslib.mobile.TelephonyIcons
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
interface MobileIconInteractor {
- /** Identifier for RAT type indicator */
- val iconGroup: Flow<SignalIcon.MobileIconGroup>
+ /** Observable for RAT type (network type) indicator */
+ val networkTypeIconGroup: Flow<MobileIconGroup>
+
/** True if this line of service is emergency-only */
val isEmergencyOnly: Flow<Boolean>
+
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
val level: Flow<Int>
+
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
val numberOfLevels: Flow<Int>
+
/** True when we want to draw an icon that makes room for the exclamation mark */
val cutOut: Flow<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
class MobileIconInteractorImpl(
- mobileStatusInfo: Flow<MobileSubscriptionModel>,
+ defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: Flow<MobileIconGroup>,
+ mobileMappingsProxy: MobileMappingsProxy,
+ connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
- override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+ private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
+
+ /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
+ override val networkTypeIconGroup: Flow<MobileIconGroup> =
+ combine(
+ mobileStatusInfo,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ ) { info, mapping, defaultGroup ->
+ val lookupKey =
+ when (val resolved = info.resolvedNetworkType) {
+ is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
+ is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
+ }
+ mapping[lookupKey] ?: defaultGroup
+ }
+
override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
override val level: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 8e67e19..cd411a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -19,29 +19,51 @@
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
- * Business layer logic for mobile subscription icons
+ * Business layer logic for the set of mobile subscription icons.
*
- * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
- * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ * This interactor represents known set of mobile subscriptions (represented by [SubscriptionInfo]).
+ * The list of subscriptions is filtered based on the opportunistic flags on the infos.
+ *
+ * It provides the default mapping between the telephony display info and the icon group that
+ * represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual
+ * icon
*/
+interface MobileIconsInteractor {
+ val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+ val defaultMobileIconGroup: Flow<MobileIconGroup>
+ val isUserSetup: Flow<Boolean>
+ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
+}
+
@SysUISingleton
-class MobileIconsInteractor
+class MobileIconsInteractorImpl
@Inject
constructor(
- private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+ private val mobileSubscriptionRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
+ private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository,
-) {
+ @Application private val scope: CoroutineScope,
+) : MobileIconsInteractor {
private val activeMobileDataSubscriptionId =
mobileSubscriptionRepo.activeMobileDataSubscriptionId
@@ -61,7 +83,7 @@
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
* and by checking which subscription is opportunistic, or which one is active.
*/
- val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ override val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
->
// Based on the old logic,
@@ -92,15 +114,29 @@
}
}
- val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+ /**
+ * Mapping from network type to [MobileIconGroup] using the config generated for the default
+ * subscription Id. This mapping is the same for every subscription.
+ */
+ override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
+ mobileSubscriptionRepo.defaultDataSubRatConfig
+ .map { mobileMappingsProxy.mapIconSets(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
+
+ /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
+ override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
+ mobileSubscriptionRepo.defaultDataSubRatConfig
+ .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
+
+ override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
/** Vends out new [MobileIconInteractor] for a particular subId */
- fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
- MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
-
- /**
- * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
- */
- private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
- mobileSubscriptionRepo.getFlowForSubId(subId)
+ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ mobileMappingsProxy,
+ mobileSubscriptionRepo.getRepoForSubId(subId),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 1405b05..67ea139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
import android.content.res.ColorStateList
+import android.view.View.GONE
+import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.isVisible
@@ -24,6 +26,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
+import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import kotlinx.coroutines.flow.collect
@@ -37,6 +40,7 @@
view: ViewGroup,
viewModel: MobileIconViewModel,
) {
+ val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
@@ -52,10 +56,20 @@
}
}
+ // Set the network type icon
+ launch {
+ viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
+ networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ }
+ }
+
// Set the tint
launch {
viewModel.tint.collect { tint ->
- iconView.imageTintList = ColorStateList.valueOf(tint)
+ val tintList = ColorStateList.valueOf(tint)
+ iconView.imageTintList = tintList
+ networkTypeView.imageTintList = tintList
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index cfabeba..cc8f6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -18,6 +18,8 @@
import android.graphics.Color
import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -26,6 +28,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -54,5 +57,15 @@
.distinctUntilChanged()
.logOutputChange(logger, "iconId($subscriptionId)")
+ /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
+ var networkTypeIcon: Flow<Icon?> =
+ iconInteractor.networkTypeIconGroup.map {
+ val desc =
+ if (it.dataContentDescription != 0)
+ ContentDescription.Resource(it.dataContentDescription)
+ else null
+ Icon.Resource(it.dataType, desc)
+ }
+
var tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
new file mode 100644
index 0000000..60bd038
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.util
+
+import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyDisplayInfo
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import javax.inject.Inject
+
+/**
+ * [MobileMappings] owns the logic on creating the map from [TelephonyDisplayInfo] to
+ * [MobileIconGroup]. It creates that hash map and also manages the creation of lookup keys. This
+ * interface allows us to proxy those calls to the static java methods in SettingsLib and also fake
+ * them out in tests
+ */
+interface MobileMappingsProxy {
+ fun mapIconSets(config: Config): Map<String, MobileIconGroup>
+ fun getDefaultIcons(config: Config): MobileIconGroup
+ fun toIconKey(@NetworkType networkType: Int): String
+ fun toIconKeyOverride(@NetworkType networkType: Int): String
+}
+
+/** Injectable wrapper class for [MobileMappings] */
+class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy {
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> =
+ MobileMappings.mapIconSets(config)
+
+ override fun getDefaultIcons(config: Config): MobileIconGroup =
+ MobileMappings.getDefaultIcons(config)
+
+ override fun toIconKey(@NetworkType networkType: Int): String =
+ MobileMappings.toIconKey(networkType)
+
+ override fun toIconKeyOverride(networkType: Int): String =
+ MobileMappings.toDisplayIconKey(networkType)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 28a9b97..cf4106c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -61,7 +61,7 @@
* animation to and from the parent dialog.
*/
@JvmOverloads
- fun onUserListItemClicked(
+ open fun onUserListItemClicked(
record: UserRecord,
dialogShower: DialogShower? = null,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index f0a50de..637fac0 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -44,11 +44,6 @@
*
* The generic type T is expected to contain all the information necessary for the subclasses to
* display the view in a certain state, since they receive <T> in [updateView].
- *
- * @property windowTitle the title to use for the window that displays the temporary view. Should be
- * normally cased, like "Window Title".
- * @property wakeReason a string used for logging if we needed to wake the screen in order to
- * display the temporary view. Should be screaming snake cased, like WAKE_REASON.
*/
abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
internal val context: Context,
@@ -59,8 +54,6 @@
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
- private val windowTitle: String,
- private val wakeReason: String,
) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -72,7 +65,6 @@
type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -100,29 +92,40 @@
fun displayView(newInfo: T) {
val currentDisplayInfo = displayInfo
- if (currentDisplayInfo != null) {
+ if (currentDisplayInfo != null &&
+ currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
+ // We're already displaying information in the correctly-titled window, so we just need
+ // to update the view.
currentDisplayInfo.info = newInfo
updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else {
- // The view is new, so set up all our callbacks and inflate the view
+ if (currentDisplayInfo != null) {
+ // We're already displaying information but that information is under a different
+ // window title. So, we need to remove the old window with the old title and add a
+ // new window with the new title.
+ removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}")
+ }
+
+ // At this point, we're guaranteed to no longer be displaying a view.
+ // So, set up all our callbacks and inflate the view.
configurationController.addCallback(displayScaleListener)
// Wake the screen if necessary so the user will see the view. (Per b/239426653, we want
// the view to show over the dream state, so we should only wake up if the screen is
// completely off.)
if (!powerManager.isScreenOn) {
powerManager.wakeUp(
- SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:$wakeReason",
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "com.android.systemui:${newInfo.wakeReason}",
)
}
- logger.logChipAddition()
+ logger.logViewAddition(newInfo.windowTitle)
inflateAndUpdateView(newInfo)
}
// Cancel and re-set the view timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
- newInfo.getTimeoutMs().toInt(),
+ newInfo.timeoutMs,
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -147,7 +150,12 @@
val newDisplayInfo = DisplayInfo(newView, newInfo)
displayInfo = newDisplayInfo
updateView(newDisplayInfo.info, newDisplayInfo.view)
- windowManager.addView(newView, windowLayoutParams)
+
+ val paramsWithTitle = WindowManager.LayoutParams().also {
+ it.copyFrom(windowLayoutParams)
+ it.title = newInfo.windowTitle
+ }
+ windowManager.addView(newView, paramsWithTitle)
animateViewIn(newView)
}
@@ -177,7 +185,7 @@
val currentView = currentDisplayInfo.view
animateViewOut(currentView) { windowManager.removeView(currentView) }
- logger.logChipRemoval(removalReason)
+ logger.logViewRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
// Re-set to null immediately (instead as part of the animation end runnable) so
// that if a new view event comes in while this view is animating out, we still display the
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index 4fe753a..cbb5002 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -19,12 +19,24 @@
/**
* A superclass view state used with [TemporaryViewDisplayController].
*/
-interface TemporaryViewInfo {
+abstract class TemporaryViewInfo {
/**
- * Returns the amount of time the given view state should display on the screen before it times
- * out and disappears.
+ * The title to use for the window that displays the temporary view. Should be normally cased,
+ * like "Window Title".
*/
- fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
+ abstract val windowTitle: String
+
+ /**
+ * A string used for logging if we needed to wake the screen in order to display the temporary
+ * view. Should be screaming snake cased, like WAKE_REASON.
+ */
+ abstract val wakeReason: String
+
+ /**
+ * The amount of time the given view state should display on the screen before it times out and
+ * disappears.
+ */
+ open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
}
-const val DEFAULT_TIMEOUT_MILLIS = 10000L
+const val DEFAULT_TIMEOUT_MILLIS = 10000
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index a7185cb..428a104 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -24,13 +24,13 @@
internal val buffer: LogBuffer,
internal val tag: String,
) {
- /** Logs that we added the chip to a new window. */
- fun logChipAddition() {
- buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" })
+ /** Logs that we added the view in a window titled [windowTitle]. */
+ fun logViewAddition(windowTitle: String) {
+ buffer.log(tag, LogLevel.DEBUG, { str1 = windowTitle }, { "View added. window=$str1" })
}
/** Logs that we removed the chip for the given [reason]. */
- fun logChipRemoval(reason: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" })
+ fun logViewRemoval(reason: String) {
+ buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" })
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index cd7bd2d..87b6e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -38,9 +38,6 @@
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.common.MediaTttUtils
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,14 +61,11 @@
* Only one chipbar may be shown at a time.
* TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
* need to maintain a priority ordering?
- *
- * TODO(b/245610654): Remove all media-related items from this class so it's just for generic
- * chipbars.
*/
@SysUISingleton
open class ChipbarCoordinator @Inject constructor(
context: Context,
- @MediaTttSenderLogger logger: MediaTttLogger,
+ logger: ChipbarLogger,
windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -81,7 +75,7 @@
private val falsingCollector: FalsingCollector,
private val viewUtil: ViewUtil,
private val vibratorHelper: VibratorHelper,
-) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
+) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
windowManager,
@@ -90,8 +84,6 @@
configurationController,
powerManager,
R.layout.chipbar,
- MediaTttUtils.WINDOW_TITLE,
- MediaTttUtils.WAKE_REASON,
) {
private lateinit var parent: ChipbarRootView
@@ -106,7 +98,16 @@
newInfo: ChipbarInfo,
currentView: ViewGroup
) {
- // TODO(b/245610654): Adding logging here.
+ logger.logViewUpdate(
+ newInfo.windowTitle,
+ newInfo.text.loadText(context),
+ when (newInfo.endItem) {
+ null -> "null"
+ is ChipbarEndItem.Loading -> "loading"
+ is ChipbarEndItem.Error -> "error"
+ is ChipbarEndItem.Button -> "button(${newInfo.endItem.text.loadText(context)})"
+ }
+ )
// Detect falsing touches on the chip.
parent = currentView.requireViewById(R.id.chipbar_root_view)
@@ -204,5 +205,4 @@
}
}
-const val SENDER_TAG = "MediaTapToTransferSender"
private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 57fde87..6237365 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -37,7 +37,10 @@
val text: Text,
val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null,
-) : TemporaryViewInfo
+ override val windowTitle: String,
+ override val wakeReason: String,
+ override val timeoutMs: Int,
+) : TemporaryViewInfo()
/** The possible items to display at the end of the chipbar. */
sealed class ChipbarEndItem {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
new file mode 100644
index 0000000..e477cd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewLogger
+import com.android.systemui.temporarydisplay.dagger.ChipbarLog
+import javax.inject.Inject
+
+/** A logger for the chipbar. */
+@SysUISingleton
+class ChipbarLogger
+@Inject
+constructor(
+ @ChipbarLog buffer: LogBuffer,
+) : TemporaryViewLogger(buffer, "ChipbarLog") {
+ /**
+ * Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
+ * [endItemDesc].
+ */
+ fun logViewUpdate(windowTitle: String, text: String?, endItemDesc: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = windowTitle
+ str2 = text
+ str3 = endItemDesc
+ },
+ { "Chipbar updated. window=$str1 text=$str2 endItem=$str3" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
index 581dafa3..5f101f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.temporarydisplay.dagger
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
-}
+import javax.inject.Qualifier
+
+/** Status bar connectivity logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class ChipbarLog
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
new file mode 100644
index 0000000..cf0a183
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.plugins.log.LogBuffer
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface TemporaryDisplayModule {
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @ChipbarLog
+ fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("ChipbarLog", 40)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
new file mode 100644
index 0000000..9653985
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.util.kotlin
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import java.util.function.Consumer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
+ * [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
+ * during onViewAttached() and removing during onViewRemoved()
+ */
+@JvmOverloads
+fun <T> collectFlow(
+ view: View,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+ state: Lifecycle.State = Lifecycle.State.CREATED,
+) {
+ view.repeatWhenAttached { repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 42d7d52..44f6d03 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -47,7 +47,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
+import com.android.systemui.wallpapers.canvas.WallpaperLocalColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
@@ -521,7 +521,7 @@
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
private WallpaperManager mWallpaperManager;
- private final WallpaperColorExtractor mWallpaperColorExtractor;
+ private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@@ -543,9 +543,9 @@
super();
setFixedSizeAllowed(true);
setShowForAllUsers(true);
- mWallpaperColorExtractor = new WallpaperColorExtractor(
+ mWallpaperLocalColorExtractor = new WallpaperLocalColorExtractor(
mBackgroundExecutor,
- new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -570,7 +570,7 @@
// if the number of pages is already computed, transmit it to the color extractor
if (mPagesComputed) {
- mWallpaperColorExtractor.onPageChanged(mPages);
+ mWallpaperLocalColorExtractor.onPageChanged(mPages);
}
}
@@ -597,7 +597,7 @@
public void onDestroy() {
getDisplayContext().getSystemService(DisplayManager.class)
.unregisterDisplayListener(this);
- mWallpaperColorExtractor.cleanUp();
+ mWallpaperLocalColorExtractor.cleanUp();
unloadBitmap();
}
@@ -813,7 +813,7 @@
@VisibleForTesting
void recomputeColorExtractorMiniBitmap() {
- mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+ mWallpaperLocalColorExtractor.onBitmapChanged(mBitmap);
}
@VisibleForTesting
@@ -830,14 +830,14 @@
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
// this call will activate the offset notifications
// if no colors were being processed before
- mWallpaperColorExtractor.addLocalColorsAreas(regions);
+ mWallpaperLocalColorExtractor.addLocalColorsAreas(regions);
}
@Override
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
// this call will deactivate the offset notifications
// if we are no longer processing colors
- mWallpaperColorExtractor.removeLocalColorAreas(regions);
+ mWallpaperLocalColorExtractor.removeLocalColorAreas(regions);
}
@Override
@@ -853,7 +853,7 @@
if (pages != mPages || !mPagesComputed) {
mPages = pages;
mPagesComputed = true;
- mWallpaperColorExtractor.onPageChanged(mPages);
+ mWallpaperLocalColorExtractor.onPageChanged(mPages);
}
}
@@ -881,7 +881,7 @@
.getSystemService(WindowManager.class)
.getCurrentWindowMetrics()
.getBounds();
- mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+ mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height());
}
@@ -902,7 +902,7 @@
: mBitmap.isRecycled() ? "recycled"
: mBitmap.getWidth() + "x" + mBitmap.getHeight());
- mWallpaperColorExtractor.dump(prefix, fd, out, args);
+ mWallpaperLocalColorExtractor.dump(prefix, fd, out, args);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
rename to packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
index e2e4555..6cac5c9 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
@@ -45,14 +45,14 @@
* It uses a background executor, and uses callbacks to inform that the work is done.
* It uses a downscaled version of the wallpaper to extract the colors.
*/
-public class WallpaperColorExtractor {
+public class WallpaperLocalColorExtractor {
private Bitmap mMiniBitmap;
@VisibleForTesting
static final int SMALL_SIDE = 128;
- private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+ private static final String TAG = WallpaperLocalColorExtractor.class.getSimpleName();
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
@@ -70,12 +70,12 @@
@Background
private final Executor mBackgroundExecutor;
- private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+ private final WallpaperLocalColorExtractorCallback mWallpaperLocalColorExtractorCallback;
/**
* Interface to handle the callbacks after the different steps of the color extraction
*/
- public interface WallpaperColorExtractorCallback {
+ public interface WallpaperLocalColorExtractorCallback {
/**
* Callback after the colors of new regions have been extracted
* @param regions the list of new regions that have been processed
@@ -103,13 +103,13 @@
/**
* Creates a new color extractor.
* @param backgroundExecutor the executor on which the color extraction will be performed
- * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+ * @param wallpaperLocalColorExtractorCallback an interface to handle the callbacks from
* the color extractor.
*/
- public WallpaperColorExtractor(@Background Executor backgroundExecutor,
- WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+ public WallpaperLocalColorExtractor(@Background Executor backgroundExecutor,
+ WallpaperLocalColorExtractorCallback wallpaperLocalColorExtractorCallback) {
mBackgroundExecutor = backgroundExecutor;
- mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+ mWallpaperLocalColorExtractorCallback = wallpaperLocalColorExtractorCallback;
}
/**
@@ -157,7 +157,7 @@
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mMiniBitmap = createMiniBitmap(bitmap);
- mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+ mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated();
recomputeColors();
}
}
@@ -206,7 +206,7 @@
boolean wasActive = isActive();
mPendingRegions.addAll(regions);
if (!wasActive && isActive()) {
- mWallpaperColorExtractorCallback.onActivated();
+ mWallpaperLocalColorExtractorCallback.onActivated();
}
processColorsInternal();
}
@@ -228,7 +228,7 @@
mPendingRegions.removeAll(regions);
regions.forEach(mProcessedRegions::remove);
if (wasActive && !isActive()) {
- mWallpaperColorExtractorCallback.onDeactivated();
+ mWallpaperLocalColorExtractorCallback.onDeactivated();
}
}
}
@@ -252,7 +252,7 @@
}
private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
- Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+ Trace.beginSection("WallpaperLocalColorExtractor#createMiniBitmap");
// if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
@@ -359,7 +359,7 @@
*/
if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
- Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+ Trace.beginSection("WallpaperLocalColorExtractor#processColorsInternal");
List<WallpaperColors> processedColors = new ArrayList<>();
for (int i = 0; i < mPendingRegions.size(); i++) {
RectF nextArea = mPendingRegions.get(i);
@@ -372,7 +372,7 @@
mPendingRegions.clear();
Trace.endSection();
- mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+ mWallpaperLocalColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 309f168..02738d5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -49,6 +49,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
@@ -58,7 +59,6 @@
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -113,7 +113,6 @@
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<FloatingTasks> mFloatingTasksOptional;
private final Optional<DesktopMode> mDesktopModeOptional;
private final CommandQueue mCommandQueue;
@@ -125,6 +124,7 @@
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final ProtoTracer mProtoTracer;
private final UserTracker mUserTracker;
+ private final NoteTaskInitializer mNoteTaskInitializer;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -176,7 +176,6 @@
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- Optional<FloatingTasks> floatingTasksOptional,
Optional<DesktopMode> desktopMode,
CommandQueue commandQueue,
ConfigurationController configurationController,
@@ -187,6 +186,7 @@
ProtoTracer protoTracer,
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
+ NoteTaskInitializer noteTaskInitializer,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -203,7 +203,7 @@
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mUserTracker = userTracker;
- mFloatingTasksOptional = floatingTasksOptional;
+ mNoteTaskInitializer = noteTaskInitializer;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -226,6 +226,8 @@
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mDesktopModeOptional.ifPresent(this::initDesktopMode);
+
+ mNoteTaskInitializer.initialize();
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
new file mode 100644
index 0000000..6c5620d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceWakeUpTriggersConfigTest : SysuiTestCase() {
+ @Mock lateinit var globalSettings: GlobalSettings
+ @Mock lateinit var dumpManager: DumpManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testShouldTriggerFaceAuthOnWakeUpFrom_inConfig_returnsTrue() {
+ val faceWakeUpTriggersConfig =
+ createFaceWakeUpTriggersConfig(
+ intArrayOf(PowerManager.WAKE_REASON_POWER_BUTTON, PowerManager.WAKE_REASON_GESTURE)
+ )
+
+ assertTrue(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_POWER_BUTTON
+ )
+ )
+ assertTrue(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_GESTURE
+ )
+ )
+ assertFalse(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_APPLICATION
+ )
+ )
+ }
+
+ private fun createFaceWakeUpTriggersConfig(wakeUpTriggers: IntArray): FaceWakeUpTriggersConfig {
+ overrideResource(
+ com.android.systemui.R.array.config_face_auth_wake_up_triggers,
+ wakeUpTriggers
+ )
+
+ return FaceWakeUpTriggersConfig(mContext.getResources(), globalSettings, dumpManager)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 627d738..61c7bb5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -44,7 +44,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
@@ -105,8 +104,6 @@
private FrameLayout mLargeClockFrame;
@Mock
private SecureSettings mSecureSettings;
- @Mock
- private FeatureFlags mFeatureFlags;
private final View mFakeSmartspaceView = new View(mContext);
@@ -143,8 +140,7 @@
mSecureSettings,
mExecutor,
mDumpManager,
- mClockEventController,
- mFeatureFlags
+ mClockEventController
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index b885d54..f9bec65 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -486,7 +486,7 @@
registeredSwipeListener.onSwipeUp();
- verify(mKeyguardUpdateMonitor).requestFaceAuth(true,
+ verify(mKeyguardUpdateMonitor).requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
}
@@ -499,16 +499,15 @@
registeredSwipeListener.onSwipeUp();
verify(mKeyguardUpdateMonitor, never())
- .requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
}
@Test
public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(true);
+ when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+ .thenReturn(true);
setupGetSecurityView();
registeredSwipeListener.onSwipeUp();
@@ -520,8 +519,8 @@
public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(false);
+ when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+ .thenReturn(false);
setupGetSecurityView();
registeredSwipeListener.onSwipeUp();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index ebfb4d4..5104f84 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -109,6 +109,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.settings.GlobalSettings;
import org.junit.After;
import org.junit.Assert;
@@ -209,6 +210,9 @@
private UiEventLogger mUiEventLogger;
@Mock
private PowerManager mPowerManager;
+ @Mock
+ private GlobalSettings mGlobalSettings;
+ private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -293,6 +297,12 @@
.when(ActivityManager::getCurrentUser);
ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
+ mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
+ mContext.getResources(),
+ mGlobalSettings,
+ mDumpManager
+ );
+
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
@@ -606,16 +616,22 @@
@Test
public void testTriesToAuthenticate_whenKeyguard() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mTestableLooper.processAllMessages();
keyguardIsVisible();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ verify(mUiEventLogger).logWithInstanceIdAndPosition(
+ eq(FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP),
+ eq(0),
+ eq(null),
+ any(),
+ eq(PowerManager.WAKE_REASON_POWER_BUTTON));
}
@Test
public void skipsAuthentication_whenStatusBarShadeLocked() {
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -629,7 +645,7 @@
STRONG_AUTH_REQUIRED_AFTER_BOOT);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -660,7 +676,7 @@
bouncerFullyVisibleAndNotGoingToSleep();
mTestableLooper.processAllMessages();
- boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
NOTIFICATION_PANEL_CLICKED);
assertThat(didFaceAuthRun).isTrue();
@@ -672,7 +688,7 @@
biometricsDisabledForCurrentUser();
mTestableLooper.processAllMessages();
- boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
NOTIFICATION_PANEL_CLICKED);
assertThat(didFaceAuthRun).isFalse();
@@ -683,7 +699,7 @@
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -691,8 +707,7 @@
// Stop scanning when bouncer becomes visible
setKeyguardBouncerVisibility(true);
clearInvocations(mFaceManager);
- mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -708,7 +723,7 @@
@Test
public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mKeyguardBypassController.canBypass()).thenReturn(true);
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
@@ -720,7 +735,7 @@
@Test
public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
@@ -731,7 +746,7 @@
@Test
public void testIgnoresAuth_whenLockdown() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -743,7 +758,7 @@
@Test
public void testTriesToAuthenticate_whenLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
@@ -767,7 +782,7 @@
@Test
public void testFaceAndFingerprintLockout_onlyFace() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -778,7 +793,7 @@
@Test
public void testFaceAndFingerprintLockout_onlyFingerprint() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -790,7 +805,7 @@
@Test
public void testFaceAndFingerprintLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -889,7 +904,7 @@
when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
.thenReturn(faceLockoutMode);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -1063,7 +1078,7 @@
@Test
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
@@ -1078,7 +1093,7 @@
@Test
public void testOccludingAppRequestsFingerprint() {
// GIVEN keyguard isn't visible (app occluding)
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
// WHEN an occluding app requests fp
@@ -1169,7 +1184,7 @@
biometricsNotDisabledThroughDevicePolicyManager();
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
setKeyguardBouncerVisibility(false /* isVisible */);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
when(mKeyguardBypassController.canBypass()).thenReturn(true);
keyguardIsVisible();
@@ -1548,7 +1563,7 @@
@Test
public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -1597,6 +1612,36 @@
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
}
+ @Test
+ public void testDreamingStopped_faceDoesNotRun() {
+ mKeyguardUpdateMonitor.dispatchDreamingStopped();
+ mTestableLooper.processAllMessages();
+
+ verify(mFaceManager, never()).authenticate(
+ any(), any(), any(), any(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testFaceWakeupTrigger_runFaceAuth_onlyOnConfiguredTriggers() {
+ // keyguard is visible
+ keyguardIsVisible();
+
+ // WHEN device wakes up from an application
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_APPLICATION);
+ mTestableLooper.processAllMessages();
+
+ // THEN face auth isn't triggered
+ verify(mFaceManager, never()).authenticate(
+ any(), any(), any(), any(), anyInt(), anyBoolean());
+
+ // WHEN device wakes up from the power button
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // THEN face auth is triggered
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1649,7 +1694,7 @@
}
private void triggerSuccessfulFaceAuth() {
- mKeyguardUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager).authenticate(any(),
any(),
mAuthenticationCallbackCaptor.capture(),
@@ -1717,7 +1762,7 @@
}
private void deviceIsInteractive() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
}
private void bouncerFullyVisible() {
@@ -1767,7 +1812,8 @@
mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
- mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager);
+ mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
+ mFaceWakeUpTriggersConfig);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
new file mode 100644
index 0000000..ae8f419
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedStateListDrawable;
+import android.util.Pair;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+public class LockIconViewControllerBaseTest extends SysuiTestCase {
+ protected static final String UNLOCKED_LABEL = "unlocked";
+ protected static final int PADDING = 10;
+
+ protected MockitoSession mStaticMockSession;
+
+ protected @Mock LockIconView mLockIconView;
+ protected @Mock AnimatedStateListDrawable mIconDrawable;
+ protected @Mock Context mContext;
+ protected @Mock Resources mResources;
+ protected @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager;
+ protected @Mock StatusBarStateController mStatusBarStateController;
+ protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ protected @Mock KeyguardViewController mKeyguardViewController;
+ protected @Mock KeyguardStateController mKeyguardStateController;
+ protected @Mock FalsingManager mFalsingManager;
+ protected @Mock AuthController mAuthController;
+ protected @Mock DumpManager mDumpManager;
+ protected @Mock AccessibilityManager mAccessibilityManager;
+ protected @Mock ConfigurationController mConfigurationController;
+ protected @Mock VibratorHelper mVibrator;
+ protected @Mock AuthRippleController mAuthRippleController;
+ protected @Mock FeatureFlags mFeatureFlags;
+ protected @Mock KeyguardTransitionRepository mTransitionRepository;
+ protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
+
+ protected LockIconViewController mUnderTest;
+
+ // Capture listeners so that they can be used to send events
+ @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+ @Captor protected ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+ protected KeyguardStateController.Callback mKeyguardStateCallback;
+
+ @Captor protected ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+ @Captor protected ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+ protected AuthController.Callback mAuthControllerCallback;
+
+ @Captor protected ArgumentCaptor<KeyguardUpdateMonitorCallback>
+ mKeyguardUpdateMonitorCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
+ @Captor protected ArgumentCaptor<Point> mPointCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ mStaticMockSession = mockitoSession()
+ .mockStatic(BurnInHelperKt.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ setupLockIconViewMocks();
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+ Rect windowBounds = new Rect(0, 0, 800, 1200);
+ when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
+ when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
+ when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
+ when(mAuthController.getScaleFactor()).thenReturn(1f);
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+
+ mUnderTest = new LockIconViewController(
+ mLockIconView,
+ mStatusBarStateController,
+ mKeyguardUpdateMonitor,
+ mKeyguardViewController,
+ mKeyguardStateController,
+ mFalsingManager,
+ mAuthController,
+ mDumpManager,
+ mAccessibilityManager,
+ mConfigurationController,
+ mDelayableExecutor,
+ mVibrator,
+ mAuthRippleController,
+ mResources,
+ new KeyguardTransitionInteractor(mTransitionRepository),
+ new KeyguardInteractor(new FakeKeyguardRepository()),
+ mFeatureFlags
+ );
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ protected Pair<Float, Point> setupUdfps() {
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+ final Point udfpsLocation = new Point(50, 75);
+ final float radius = 33f;
+ when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
+ when(mAuthController.getUdfpsRadius()).thenReturn(radius);
+
+ return new Pair(radius, udfpsLocation);
+ }
+
+ protected void setupShowLockIcon() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ }
+
+ protected void captureAuthControllerCallback() {
+ verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+ mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+ }
+
+ protected void captureKeyguardStateCallback() {
+ verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
+ mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
+ }
+
+ protected void captureStatusBarStateListener() {
+ verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
+ mStatusBarStateListener = mStatusBarStateCaptor.getValue();
+ }
+
+ protected void captureKeyguardUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+ }
+
+ protected void setupLockIconViewMocks() {
+ when(mLockIconView.getResources()).thenReturn(mResources);
+ when(mLockIconView.getContext()).thenReturn(mContext);
+ }
+
+ protected void resetLockIconView() {
+ reset(mLockIconView);
+ setupLockIconViewMocks();
+ }
+
+ protected void init(boolean useMigrationFlag) {
+ when(mFeatureFlags.isEnabled(DOZING_MIGRATION_1)).thenReturn(useMigrationFlag);
+ mUnderTest.init();
+
+ verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachCaptor.getValue().onViewAttachedToWindow(mLockIconView);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 0000000..da40595
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.keyguard;
+
+import static com.android.keyguard.LockIconView.ICON_LOCK;
+import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.hardware.biometrics.BiometricSourceType;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.doze.util.BurnInHelperKt;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
+
+ @Test
+ public void testUpdateFingerprintLocationOnInit() {
+ // GIVEN fp sensor location is available pre-attached
+ Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
+
+ // WHEN lock icon view controller is initialized and attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN lock icon view location is updated to the udfps location with UDFPS radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testUpdatePaddingBasedOnResolutionScale() {
+ // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
+ Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
+ when(mAuthController.getScaleFactor()).thenReturn(5f);
+
+ // WHEN lock icon view controller is initialized and attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN lock icon view location is updated with the scaled radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING * 5));
+ }
+
+ @Test
+ public void testUpdateLockIconLocationOnAuthenticatorsRegistered() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ init(/* useMigrationFlag= */false);
+ resetLockIconView(); // reset any method call counts for when we verify method calls later
+
+ // GIVEN fp sensor location is available post-attached
+ captureAuthControllerCallback();
+ Pair<Float, Point> udfps = setupUdfps();
+
+ // WHEN all authenticators are registered
+ mAuthControllerCallback.onAllAuthenticatorsRegistered();
+ mDelayableExecutor.runAllReady();
+
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testUpdateLockIconLocationOnUdfpsLocationChanged() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ init(/* useMigrationFlag= */false);
+ resetLockIconView(); // reset any method call counts for when we verify method calls later
+
+ // GIVEN fp sensor location is available post-attached
+ captureAuthControllerCallback();
+ Pair<Float, Point> udfps = setupUdfps();
+
+ // WHEN udfps location changes
+ mAuthControllerCallback.onUdfpsLocationChanged();
+ mDelayableExecutor.runAllReady();
+
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
+ // GIVEN Udpfs sensor location is available
+ setupUdfps();
+
+ // WHEN the view is attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon view background should be enabled
+ verify(mLockIconView).setUseBackground(true);
+ }
+
+ @Test
+ public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
+ // GIVEN Udfps sensor location is not supported
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+
+ // WHEN the view is attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon view background should be disabled
+ verify(mLockIconView).setUseBackground(false);
+ }
+
+ @Test
+ public void testUnlockIconShows_biometricUnlockedTrue() {
+ // GIVEN UDFPS sensor location is available
+ setupUdfps();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ reset(mLockIconView);
+
+ // WHEN face auth's biometric running state changes
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+
+ // THEN the unlock icon is shown
+ verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
+ }
+
+ @Test
+ public void testLockIconStartState() {
+ // GIVEN lock icon state
+ setupShowLockIcon();
+
+ // WHEN lock icon controller is initialized
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_updateToUnlock() {
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ reset(mLockIconView);
+
+ // WHEN the unlocked state changes to canDismissLockScreen=true
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the unlock should show
+ verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
+ // GIVEN udfps not enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon();
+ }
+
+ @Test
+ public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true);
+ }
+
+ @Test
+ public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN burn-in offset = 5
+ int burnInOffset = 5;
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
+
+ // GIVEN starting state for the lock icon (keyguard)
+ setupShowLockIcon();
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN dozing updates
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+ mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
+
+ // THEN the view's translation is updated to use the AoD burn-in offsets
+ verify(mLockIconView).setTranslationY(burnInOffset);
+ verify(mLockIconView).setTranslationX(burnInOffset);
+ reset(mLockIconView);
+
+ // WHEN the device is no longer dozing
+ mStatusBarStateListener.onDozingChanged(false /* isDozing */);
+ mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
+
+ // THEN the view is updated to NO translation (no burn-in offsets anymore)
+ verify(mLockIconView).setTranslationY(0);
+ verify(mLockIconView).setTranslationX(0);
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..d2c54b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.keyguard.LockIconView.ICON_LOCK
+import com.android.systemui.doze.util.getBurnInOffset
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LockIconViewControllerWithCoroutinesTest : LockIconViewControllerBaseTest() {
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps not enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false)
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon()
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN the dozing state changes
+ mUnderTest.mIsDozingCallback.accept(true)
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon()
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testLockIcon_updateToAodLock_whenUdfpsEnrolled() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon()
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN the dozing state changes
+ mUnderTest.mIsDozingCallback.accept(true)
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true)
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testBurnInOffsetsUpdated_onDozeAmountChanged() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
+
+ // GIVEN burn-in offset = 5
+ val burnInOffset = 5
+ whenever(getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
+
+ // GIVEN starting state for the lock icon (keyguard)
+ setupShowLockIcon()
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN dozing updates
+ mUnderTest.mIsDozingCallback.accept(true)
+ mUnderTest.mDozeTransitionCallback.accept(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+
+ // THEN the view's translation is updated to use the AoD burn-in offsets
+ verify(mLockIconView).setTranslationY(burnInOffset.toFloat())
+ verify(mLockIconView).setTranslationX(burnInOffset.toFloat())
+ reset(mLockIconView)
+
+ // WHEN the device is no longer dozing
+ mUnderTest.mIsDozingCallback.accept(false)
+ mUnderTest.mDozeTransitionCallback.accept(TransitionStep(AOD, LOCKSCREEN, 0f, FINISHED))
+
+ // THEN the view is updated to NO translation (no burn-in offsets anymore)
+ verify(mLockIconView).setTranslationY(0f)
+ verify(mLockIconView).setTranslationX(0f)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
index e099c92..ea16cb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -20,6 +20,7 @@
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
@@ -63,6 +64,8 @@
.isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS);
assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_SMARTSPACE))
.isEqualTo(COMPLICATION_TYPE_SMARTSPACE);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_MEDIA_ENTRY))
+ .isEqualTo(COMPLICATION_TYPE_MEDIA_ENTRY);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
index 50f27ea..0295030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
@@ -16,8 +16,11 @@
package com.android.systemui.dreams.complication;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -32,6 +35,7 @@
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.ui.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
@@ -51,6 +55,9 @@
@TestableLooper.RunWithLooper
public class DreamMediaEntryComplicationTest extends SysuiTestCase {
@Mock
+ private DreamMediaEntryComplicationComponent.Factory mComponentFactory;
+
+ @Mock
private View mView;
@Mock
@@ -89,6 +96,14 @@
when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false);
}
+ @Test
+ public void testGetRequiredTypeAvailability() {
+ final DreamMediaEntryComplication complication =
+ new DreamMediaEntryComplication(mComponentFactory);
+ assertThat(complication.getRequiredTypeAvailability()).isEqualTo(
+ COMPLICATION_TYPE_MEDIA_ENTRY);
+ }
+
/**
* Ensures clicking media entry chip adds/removes media complication.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 20a82c6..4b3b70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -88,6 +88,7 @@
flagMap,
restarter
)
+ mFeatureFlagsDebug.init()
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
@@ -255,11 +256,11 @@
broadcastReceiver.onReceive(mockContext, Intent())
broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
- setByBroadcast(0, false) // unknown id does nothing
- setByBroadcast(1, "string") // wrong type does nothing
- setByBroadcast(2, 123) // wrong type does nothing
- setByBroadcast(3, false) // wrong type does nothing
- setByBroadcast(4, 123) // wrong type does nothing
+ setByBroadcast(0, false) // unknown id does nothing
+ setByBroadcast(1, "string") // wrong type does nothing
+ setByBroadcast(2, 123) // wrong type does nothing
+ setByBroadcast(3, false) // wrong type does nothing
+ setByBroadcast(4, 123) // wrong type does nothing
verifyNoMoreInteractions(flagManager, secureSettings)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 575c142..b2dd60c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -38,8 +38,9 @@
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
+ @Mock private lateinit var restarter: Restarter
+ private val flagMap = mutableMapOf<Int, Flag<*>>()
private val serverFlagReader = ServerFlagReaderFake()
-
private val deviceConfig = DeviceConfigProxyFake()
@Before
@@ -49,7 +50,9 @@
mResources,
mSystemProperties,
deviceConfig,
- serverFlagReader)
+ serverFlagReader,
+ flagMap,
+ restarter)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
new file mode 100644
index 0000000..6f5f460
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ServerFlagReaderImplTest : SysuiTestCase() {
+
+ private val NAMESPACE = "test"
+
+ @Mock private lateinit var changeListener: ServerFlagReader.ChangeListener
+
+ private lateinit var serverFlagReader: ServerFlagReaderImpl
+ private val deviceConfig = DeviceConfigProxyFake()
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor)
+ }
+
+ @Test
+ fun testChange_alertsListener() {
+ val flag = ReleasedFlag(1)
+ serverFlagReader.listenForChanges(listOf(flag), changeListener)
+
+ deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
+ executor.runAllReady()
+
+ verify(changeListener).onChange()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 4c986bf..2c3ddd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -60,6 +60,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -112,6 +113,8 @@
private FalsingCollectorFake mFalsingCollector;
+ private @Mock CentralSurfaces mCentralSurfaces;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -258,6 +261,26 @@
verify(mKeyguardStateController).notifyKeyguardGoingAway(false);
}
+ @Test
+ public void testUpdateIsKeyguardAfterOccludeAnimationEnds() {
+ mViewMediator.mOccludeAnimationController.onLaunchAnimationEnd(
+ false /* isExpandingFullyAbove */);
+
+ // Since the updateIsKeyguard call is delayed during the animation, ensure it's called once
+ // it ends.
+ verify(mCentralSurfaces).updateIsKeyguard();
+ }
+
+ @Test
+ public void testUpdateIsKeyguardAfterOccludeAnimationIsCancelled() {
+ mViewMediator.mOccludeAnimationController.onLaunchAnimationCancelled(
+ null /* newKeyguardOccludedState */);
+
+ // Since the updateIsKeyguard call is delayed during the animation, ensure it's called if
+ // it's cancelled.
+ verify(mCentralSurfaces).updateIsKeyguard();
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -287,5 +310,7 @@
mNotificationShadeWindowControllerLazy,
() -> mActivityLaunchAnimator);
mViewMediator.start();
+
+ mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
deleted file mode 100644
index cefd68d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ /dev/null
@@ -1,476 +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.systemui.keyguard;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.keyguard.LockIconView.ICON_LOCK;
-import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.AnimatedStateListDrawable;
-import android.hardware.biometrics.BiometricSourceType;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Pair;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.KeyguardViewController;
-import com.android.keyguard.LockIconView;
-import com.android.keyguard.LockIconViewController;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.AuthRippleController;
-import com.android.systemui.doze.util.BurnInHelperKt;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class LockIconViewControllerTest extends SysuiTestCase {
- private static final String UNLOCKED_LABEL = "unlocked";
- private static final int PADDING = 10;
-
- private MockitoSession mStaticMockSession;
-
- private @Mock LockIconView mLockIconView;
- private @Mock AnimatedStateListDrawable mIconDrawable;
- private @Mock Context mContext;
- private @Mock Resources mResources;
- private @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager;
- private @Mock StatusBarStateController mStatusBarStateController;
- private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private @Mock KeyguardViewController mKeyguardViewController;
- private @Mock KeyguardStateController mKeyguardStateController;
- private @Mock FalsingManager mFalsingManager;
- private @Mock AuthController mAuthController;
- private @Mock DumpManager mDumpManager;
- private @Mock AccessibilityManager mAccessibilityManager;
- private @Mock ConfigurationController mConfigurationController;
- private @Mock VibratorHelper mVibrator;
- private @Mock AuthRippleController mAuthRippleController;
- private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
-
- private LockIconViewController mLockIconViewController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- private View.OnAttachStateChangeListener mAttachListener;
-
- @Captor private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
- ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
- private KeyguardStateController.Callback mKeyguardStateCallback;
-
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
- private AuthController.Callback mAuthControllerCallback;
-
- @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback>
- mKeyguardUpdateMonitorCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
- private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
-
- @Captor private ArgumentCaptor<Point> mPointCaptor;
-
- @Before
- public void setUp() throws Exception {
- mStaticMockSession = mockitoSession()
- .mockStatic(BurnInHelperKt.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
- MockitoAnnotations.initMocks(this);
-
- setupLockIconViewMocks();
- when(mContext.getResources()).thenReturn(mResources);
- when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
- Rect windowBounds = new Rect(0, 0, 800, 1200);
- when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
- when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
- when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
- when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
- when(mAuthController.getScaleFactor()).thenReturn(1f);
-
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-
- mLockIconViewController = new LockIconViewController(
- mLockIconView,
- mStatusBarStateController,
- mKeyguardUpdateMonitor,
- mKeyguardViewController,
- mKeyguardStateController,
- mFalsingManager,
- mAuthController,
- mDumpManager,
- mAccessibilityManager,
- mConfigurationController,
- mDelayableExecutor,
- mVibrator,
- mAuthRippleController,
- mResources
- );
- }
-
- @After
- public void tearDown() {
- mStaticMockSession.finishMocking();
- }
-
- @Test
- public void testUpdateFingerprintLocationOnInit() {
- // GIVEN fp sensor location is available pre-attached
- Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
-
- // WHEN lock icon view controller is initialized and attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN lock icon view location is updated to the udfps location with UDFPS radius
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testUpdatePaddingBasedOnResolutionScale() {
- // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
- Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
- when(mAuthController.getScaleFactor()).thenReturn(5f);
-
- // WHEN lock icon view controller is initialized and attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN lock icon view location is updated with the scaled radius
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING * 5));
- }
-
- @Test
- public void testUpdateLockIconLocationOnAuthenticatorsRegistered() {
- // GIVEN fp sensor location is not available pre-init
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
- when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- resetLockIconView(); // reset any method call counts for when we verify method calls later
-
- // GIVEN fp sensor location is available post-attached
- captureAuthControllerCallback();
- Pair<Float, Point> udfps = setupUdfps();
-
- // WHEN all authenticators are registered
- mAuthControllerCallback.onAllAuthenticatorsRegistered();
- mDelayableExecutor.runAllReady();
-
- // THEN lock icon view location is updated with the same coordinates as auth controller vals
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testUpdateLockIconLocationOnUdfpsLocationChanged() {
- // GIVEN fp sensor location is not available pre-init
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
- when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- resetLockIconView(); // reset any method call counts for when we verify method calls later
-
- // GIVEN fp sensor location is available post-attached
- captureAuthControllerCallback();
- Pair<Float, Point> udfps = setupUdfps();
-
- // WHEN udfps location changes
- mAuthControllerCallback.onUdfpsLocationChanged();
- mDelayableExecutor.runAllReady();
-
- // THEN lock icon view location is updated with the same coordinates as auth controller vals
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
- // GIVEN Udpfs sensor location is available
- setupUdfps();
-
- mLockIconViewController.init();
- captureAttachListener();
-
- // WHEN the view is attached
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon view background should be enabled
- verify(mLockIconView).setUseBackground(true);
- }
-
- @Test
- public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
- // GIVEN Udfps sensor location is not supported
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-
- mLockIconViewController.init();
- captureAttachListener();
-
- // WHEN the view is attached
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon view background should be disabled
- verify(mLockIconView).setUseBackground(false);
- }
-
- @Test
- public void testUnlockIconShows_biometricUnlockedTrue() {
- // GIVEN UDFPS sensor location is available
- setupUdfps();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureKeyguardUpdateMonitorCallback();
-
- // GIVEN user has unlocked with a biometric auth (ie: face auth)
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
- reset(mLockIconView);
-
- // WHEN face auth's biometric running state changes
- mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
- BiometricSourceType.FACE);
-
- // THEN the unlock icon is shown
- verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
- }
-
- @Test
- public void testLockIconStartState() {
- // GIVEN lock icon state
- setupShowLockIcon();
-
- // WHEN lock icon controller is initialized
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon should show
- verify(mLockIconView).updateIcon(ICON_LOCK, false);
- }
-
- @Test
- public void testLockIcon_updateToUnlock() {
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureKeyguardStateCallback();
- reset(mLockIconView);
-
- // WHEN the unlocked state changes to canDismissLockScreen=true
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
- mKeyguardStateCallback.onUnlockedChanged();
-
- // THEN the unlock should show
- verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
- }
-
- @Test
- public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
- // GIVEN udfps not enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
-
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN the dozing state changes
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-
- // THEN the icon is cleared
- verify(mLockIconView).clearIcon();
- }
-
- @Test
- public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
- // GIVEN udfps enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN the dozing state changes
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-
- // THEN the AOD lock icon should show
- verify(mLockIconView).updateIcon(ICON_LOCK, true);
- }
-
- @Test
- public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
- // GIVEN udfps enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
- // GIVEN burn-in offset = 5
- int burnInOffset = 5;
- when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
-
- // GIVEN starting state for the lock icon (keyguard)
- setupShowLockIcon();
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN dozing updates
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
- mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
-
- // THEN the view's translation is updated to use the AoD burn-in offsets
- verify(mLockIconView).setTranslationY(burnInOffset);
- verify(mLockIconView).setTranslationX(burnInOffset);
- reset(mLockIconView);
-
- // WHEN the device is no longer dozing
- mStatusBarStateListener.onDozingChanged(false /* isDozing */);
- mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
-
- // THEN the view is updated to NO translation (no burn-in offsets anymore)
- verify(mLockIconView).setTranslationY(0);
- verify(mLockIconView).setTranslationX(0);
-
- }
- private Pair<Float, Point> setupUdfps() {
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
- final Point udfpsLocation = new Point(50, 75);
- final float radius = 33f;
- when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
- when(mAuthController.getUdfpsRadius()).thenReturn(radius);
-
- return new Pair(radius, udfpsLocation);
- }
-
- private void setupShowLockIcon() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
- }
-
- private void captureAuthControllerCallback() {
- verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
- mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
- }
-
- private void captureAttachListener() {
- verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachListener = mAttachCaptor.getValue();
- }
-
- private void captureKeyguardStateCallback() {
- verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
- mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
- }
-
- private void captureStatusBarStateListener() {
- verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
- mStatusBarStateListener = mStatusBarStateCaptor.getValue();
- }
-
- private void captureKeyguardUpdateMonitorCallback() {
- verify(mKeyguardUpdateMonitor).registerCallback(
- mKeyguardUpdateMonitorCallbackCaptor.capture());
- mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
- }
-
- private void setupLockIconViewMocks() {
- when(mLockIconView.getResources()).thenReturn(mResources);
- when(mLockIconView.getContext()).thenReturn(mContext);
- }
-
- private void resetLockIconView() {
- reset(mLockIconView);
- setupLockIconViewMocks();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index e99c139..f18acba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -15,10 +15,10 @@
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import com.android.systemui.animation.Expandable
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
@@ -29,24 +29,27 @@
* This class is abstract to force tests to provide extensions of it as the system that references
* these configs uses each implementation's class type to refer to them.
*/
-abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+abstract class FakeKeyguardQuickAffordanceConfig(
+ override val key: String,
+) : KeyguardQuickAffordanceConfig {
- var onClickedResult: OnClickedResult = OnClickedResult.Handled
+ var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
- private val _state =
- MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
- KeyguardQuickAffordanceConfig.State.Hidden
+ private val _lockScreenState =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.LockScreenState>(
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
)
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ _lockScreenState
- override fun onQuickAffordanceClicked(
+ override fun onTriggered(
expandable: Expandable?,
- ): OnClickedResult {
- return onClickedResult
+ ): OnTriggeredResult {
+ return onTriggeredResult
}
- suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
- _state.value = state
+ suspend fun setState(lockScreenState: KeyguardQuickAffordanceConfig.LockScreenState) {
+ _lockScreenState.value = lockScreenState
// Yield to allow the test's collection coroutine to "catch up" and collect this value
// before the test continues to the next line.
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
similarity index 83%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 9a91ea91..c94cec6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -122,8 +122,8 @@
emptyList()
}
)
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
if (canShowWhileLocked) {
verify(controlsListingController).addCallback(callbackCaptor.capture())
@@ -139,9 +139,9 @@
assertThat(values.last())
.isInstanceOf(
if (isVisibleExpected) {
- KeyguardQuickAffordanceConfig.State.Visible::class.java
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java
} else {
- KeyguardQuickAffordanceConfig.State.Hidden::class.java
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
}
)
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 65%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index a809f05..659c1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -23,7 +23,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
@@ -72,11 +72,11 @@
whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
assertThat(values.last())
- .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
job.cancel()
}
@@ -91,31 +91,32 @@
whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
assertThat(values.last())
- .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
job.cancel()
}
@Test
- fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+ fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is true`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
- val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+ val onClickedResult = underTest.onTriggered(expandable)
- assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
- assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+ assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked).isTrue()
}
@Test
- fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+ fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is false`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
- val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+ val onClickedResult = underTest.onTriggered(expandable)
- assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
- assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+ assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked)
+ .isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 70%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 329c4db..61a3f9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -1,26 +1,26 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
@@ -56,9 +56,9 @@
@Test
fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -77,8 +77,8 @@
fun `affordance - scanner activity changed - delivers model with updated intent`() =
runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -93,8 +93,8 @@
@Test
fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -109,34 +109,35 @@
@Test
fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
callbackCaptor.value.onQRCodeScannerPreferenceChanged()
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
verify(controller).removeCallback(callbackCaptor.value)
}
@Test
- fun onQuickAffordanceClicked() {
- assertThat(underTest.onQuickAffordanceClicked(mock()))
+ fun onQuickAffordanceTriggered() {
+ assertThat(underTest.onTriggered(mock()))
.isEqualTo(
- OnClickedResult.StartActivity(
+ OnTriggeredResult.StartActivity(
intent = INTENT_1,
canShowWhileLocked = true,
)
)
}
- private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
- val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+ private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
+ assertThat(latest)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ val visibleState = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
assertThat(visibleState.icon).isNotNull()
assertThat(visibleState.icon.contentDescription).isNotNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 74%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 98dc4c4..c05beef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -67,11 +67,11 @@
@Test
fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
setUpState()
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
assertThat(visibleModel.icon)
.isEqualTo(
Icon.Loaded(
@@ -88,11 +88,11 @@
@Test
fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
setUpState(isWalletEnabled = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -100,11 +100,11 @@
@Test
fun `affordance - query not successful - model is none`() = runBlockingTest {
setUpState(isWalletQuerySuccessful = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -112,11 +112,11 @@
@Test
fun `affordance - missing icon - model is none`() = runBlockingTest {
setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -124,24 +124,24 @@
@Test
fun `affordance - no selected card - model is none`() = runBlockingTest {
setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@Test
- fun onQuickAffordanceClicked() {
+ fun onQuickAffordanceTriggered() {
val animationController: ActivityLaunchAnimator.Controller = mock()
val expandable: Expandable = mock {
whenever(this.activityLaunchController()).thenReturn(animationController)
}
- assertThat(underTest.onQuickAffordanceClicked(expandable))
- .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+ assertThat(underTest.onTriggered(expandable))
+ .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled)
verify(walletController)
.startQuickAccessUiIntent(
activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b4d5464..7116cc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,11 +25,12 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -211,7 +212,11 @@
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -224,8 +229,14 @@
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
- object : FakeKeyguardQuickAffordanceConfig() {},
- object : FakeKeyguardQuickAffordanceConfig() {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {},
),
),
),
@@ -237,30 +248,30 @@
}
@Test
- fun onQuickAffordanceClicked() = runBlockingTest {
+ fun onQuickAffordanceTriggered() = runBlockingTest {
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
)
homeControls.setState(
- state =
- KeyguardQuickAffordanceConfig.State.Visible(
+ lockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = DRAWABLE,
)
)
- homeControls.onClickedResult =
+ homeControls.onTriggeredResult =
if (startActivity) {
- KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = INTENT,
canShowWhileLocked = canShowWhileLocked,
)
} else {
- KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
- underTest.onQuickAffordanceClicked(
- configKey = homeControls::class,
+ underTest.onQuickAffordanceTriggered(
+ configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
expandable = expandable,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 65fd6e5..ae32ba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,13 +22,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -69,9 +70,21 @@
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWallet =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScanner =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
@@ -99,11 +112,11 @@
@Test
fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
- val configKey = homeControls::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
- toggle = KeyguardQuickAffordanceToggleState.On,
+ activationState = ActivationState.Active,
)
)
@@ -124,15 +137,15 @@
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On)
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
job.cancel()
}
@Test
fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
- val configKey = quickAccessWallet::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
@@ -154,7 +167,7 @@
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported)
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
job.cancel()
}
@@ -162,7 +175,7 @@
fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
repository.setDozing(true)
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
@@ -182,7 +195,7 @@
runBlockingTest {
repository.setKeyguardShowing(false)
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
index e68c43f..13e2768 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import kotlin.reflect.KClass
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
class FakeKeyguardQuickAffordanceRegistry(
@@ -33,11 +33,8 @@
}
override fun get(
- configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+ key: String,
): FakeKeyguardQuickAffordanceConfig {
- return configsByPosition.values
- .flatten()
- .associateBy { config -> config::class }
- .getValue(configClass)
+ return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
}
}
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 d674c89..f73d1ec 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
@@ -23,15 +23,16 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,7 +41,6 @@
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -81,9 +81,21 @@
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
.thenReturn(RETURNED_BURN_IN_OFFSET)
- homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControlsQuickAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWalletAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScannerAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -489,42 +501,42 @@
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
- ): KClass<out FakeKeyguardQuickAffordanceConfig> {
+ ): String {
val config =
when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
}
- val state =
+ val lockScreenState =
if (testConfig.isVisible) {
if (testConfig.intent != null) {
- config.onClickedResult =
- KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ config.onTriggeredResult =
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = testConfig.intent,
canShowWhileLocked = testConfig.canShowWhileLocked,
)
}
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
- toggle =
+ activationState =
when (testConfig.isActivated) {
- true -> KeyguardQuickAffordanceToggleState.On
- false -> KeyguardQuickAffordanceToggleState.Off
- null -> KeyguardQuickAffordanceToggleState.NotSupported
+ true -> ActivationState.Active
+ false -> ActivationState.Inactive
+ null -> ActivationState.NotSupported
}
)
} else {
- KeyguardQuickAffordanceConfig.State.Hidden
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
- config.setState(state)
- return config::class
+ config.setState(lockScreenState)
+ return config.key
}
private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig,
- configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
+ configKey: String,
) {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 071604d..920801f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -31,7 +31,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.testing.FakeNotifPanelEvents
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -89,7 +89,7 @@
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
- private val notifPanelEvents = FakeNotifPanelEvents()
+ private val notifPanelEvents = ShadeExpansionStateManager()
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
@@ -346,7 +346,7 @@
@Test
fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
- notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+ notifPanelEvents.notifyExpandImmediateChange(true)
goToLockscreen()
enterGuidedTransformation()
whenever(lockHost.visible).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index fdeb3f5..ad19bc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -80,6 +81,7 @@
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var chipbarLogger: ChipbarLogger
@Mock private lateinit var logger: MediaTttLogger
@Mock private lateinit var mediaTttFlags: MediaTttFlags
@Mock private lateinit var packageManager: PackageManager
@@ -122,7 +124,7 @@
chipbarCoordinator =
FakeChipbarCoordinator(
context,
- logger,
+ chipbarLogger,
windowManager,
fakeExecutor,
accessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
new file mode 100644
index 0000000..f20c6a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.notetask
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.content.Intent
+import android.os.UserManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.KeyEvent
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskController].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskControllerTest : SysuiTestCase() {
+
+ private val notesIntent = Intent(NOTES_ACTION)
+
+ @Mock lateinit var context: Context
+ @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
+ @Mock lateinit var floatingTasks: FloatingTasks
+ @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+ @Mock lateinit var keyguardManager: KeyguardManager
+ @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
+ @Mock lateinit var optionalUserManager: Optional<UserManager>
+ @Mock lateinit var userManager: UserManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
+ whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
+ whenever(userManager.isUserUnlocked).thenReturn(true)
+ }
+
+ private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
+ return NoteTaskController(
+ context = context,
+ intentResolver = noteTaskIntentResolver,
+ optionalFloatingTasks = optionalFloatingTasks,
+ optionalKeyguardManager = optionalKeyguardManager,
+ optionalUserManager = optionalUserManager,
+ isEnabled = isEnabled,
+ )
+ }
+
+ @Test
+ fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(floatingTasks).showOrSetStashed(notesIntent)
+ verify(context, never()).startActivity(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() {
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+ whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+ whenever(optionalUserManager.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+ whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_flagDisabled_shouldDoNothing() {
+ createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_userIsLocked_shouldDoNothing() {
+ whenever(userManager.isUserUnlocked).thenReturn(false)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
new file mode 100644
index 0000000..f344c8d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.notetask
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskController].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskInitializerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskInitializerTest : SysuiTestCase() {
+
+ @Mock lateinit var commandQueue: CommandQueue
+ @Mock lateinit var floatingTasks: FloatingTasks
+ @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(optionalFloatingTasks.isPresent).thenReturn(true)
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ }
+
+ private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
+ return NoteTaskInitializer(
+ optionalFloatingTasks = optionalFloatingTasks,
+ lazyNoteTaskController = mock(),
+ commandQueue = commandQueue,
+ isEnabled = isEnabled,
+ )
+ }
+
+ @Test
+ fun initialize_shouldAddCallbacks() {
+ createNoteTaskInitializer().initialize()
+
+ verify(commandQueue).addCallback(any())
+ }
+
+ @Test
+ fun initialize_flagDisabled_shouldDoNothing() {
+ createNoteTaskInitializer(isEnabled = false).initialize()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+
+ @Test
+ fun initialize_floatingTasksNotPresent_shouldDoNothing() {
+ whenever(optionalFloatingTasks.isPresent).thenReturn(false)
+
+ createNoteTaskInitializer().initialize()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
new file mode 100644
index 0000000..dd2cc2f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskIntentResolver].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskIntentResolverTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskIntentResolverTest : SysuiTestCase() {
+
+ @Mock lateinit var packageManager: PackageManager
+
+ private lateinit var resolver: NoteTaskIntentResolver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ resolver = NoteTaskIntentResolver(packageManager)
+ }
+
+ private fun createResolveInfo(
+ packageName: String = "PackageName",
+ activityInfo: ActivityInfo? = null,
+ ): ResolveInfo {
+ return ResolveInfo().apply {
+ serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo().apply { this.packageName = packageName }
+ }
+ this.activityInfo = activityInfo
+ }
+ }
+
+ private fun createActivityInfo(
+ name: String? = "ActivityName",
+ exported: Boolean = true,
+ enabled: Boolean = true,
+ showWhenLocked: Boolean = true,
+ turnScreenOn: Boolean = true,
+ ): ActivityInfo {
+ return ActivityInfo().apply {
+ this.name = name
+ this.exported = exported
+ this.enabled = enabled
+ if (showWhenLocked) {
+ flags = flags or ActivityInfo.FLAG_SHOW_WHEN_LOCKED
+ }
+ if (turnScreenOn) {
+ flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON
+ }
+ }
+ }
+
+ private fun givenQueryIntentActivities(block: () -> List<ResolveInfo>) {
+ whenever(packageManager.queryIntentActivities(any(), any<ResolveInfoFlags>()))
+ .thenReturn(block())
+ }
+
+ private fun givenResolveActivity(block: () -> ResolveInfo?) {
+ whenever(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(block())
+ }
+
+ @Test
+ fun resolveIntent_shouldReturnNotesIntent() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) }
+
+ val actual = resolver.resolveIntent()
+
+ val expected =
+ Intent(NOTES_ACTION)
+ .setComponent(ComponentName("PackageName", "ActivityName"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // Compares the string representation of both intents, as they are different instances.
+ assertThat(actual.toString()).isEqualTo(expected.toString())
+ }
+
+ @Test
+ fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(enabled = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoExportedIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(exported = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoShowWhenLockedIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(showWhenLocked = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoTurnScreenOnIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(turnScreenOn = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoNameIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = null)) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = null) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_resolveActivityIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { null }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_packageNameIsBlank_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo(packageName = "")) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
+ givenQueryIntentActivities { emptyList() }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index bc27bbc..3131f60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
@@ -41,20 +42,27 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UserDetailViewAdapterTest : SysuiTestCase() {
- @Mock private lateinit var mUserSwitcherController: UserSwitcherController
- @Mock private lateinit var mParent: ViewGroup
- @Mock private lateinit var mUserDetailItemView: UserDetailItemView
- @Mock private lateinit var mOtherView: View
- @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
- @Mock private lateinit var mLayoutInflater: LayoutInflater
+ @Mock
+ private lateinit var mUserSwitcherController: UserSwitcherController
+ @Mock
+ private lateinit var mParent: ViewGroup
+ @Mock
+ private lateinit var mUserDetailItemView: UserDetailItemView
+ @Mock
+ private lateinit var mOtherView: View
+ @Mock
+ private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+ @Mock
+ private lateinit var mLayoutInflater: LayoutInflater
private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
private lateinit var adapter: UserDetailView.Adapter
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -67,10 +75,12 @@
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater)
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
- .thenReturn(mInflatedUserDetailItemView)
+ .thenReturn(mInflatedUserDetailItemView)
`when`(mParent.context).thenReturn(mContext)
- adapter = UserDetailView.Adapter(mContext, mUserSwitcherController, uiEventLogger,
- falsingManagerFake)
+ adapter = UserDetailView.Adapter(
+ mContext, mUserSwitcherController, uiEventLogger,
+ falsingManagerFake
+ )
mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
@@ -145,6 +155,15 @@
assertNull(adapter.users.find { it.isManageUsers })
}
+ @Test
+ fun clickDismissDialog() {
+ val shower: UserSwitchDialogController.DialogShower =
+ mock(UserSwitchDialogController.DialogShower::class.java)
+ adapter.injectDialogShower(shower)
+ adapter.onUserListItemClicked(createUserRecord(current = true, guest = false), shower)
+ verify(shower).dismiss()
+ }
+
private fun createUserRecord(current: Boolean, guest: Boolean) =
UserRecord(
UserInfo(0 /* id */, "name", 0 /* flags */),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 02f28a2..ac4dd49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -438,8 +438,6 @@
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
mMainHandler = new Handler(Looper.getMainLooper());
- NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
- new NotificationPanelViewController.PanelEventsEmitter();
mNotificationPanelViewController = new NotificationPanelViewController(
mView,
@@ -495,7 +493,6 @@
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
mNotificationListContainer,
- panelEventsEmitter,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
@@ -1597,7 +1594,7 @@
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor).requestFaceAuth(true,
+ verify(mUpdateMonitor).requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
}
@@ -1607,7 +1604,7 @@
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
+ when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1622,7 +1619,7 @@
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
+ when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1642,7 +1639,7 @@
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
}
@Test
@@ -1653,7 +1650,7 @@
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
deleted file mode 100644
index d052138..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade.testing
-
-import com.android.systemui.shade.NotifPanelEvents
-
-/** Fake implementation of [NotifPanelEvents] for testing. */
-class FakeNotifPanelEvents : NotifPanelEvents {
-
- private val listeners = mutableListOf<NotifPanelEvents.Listener>()
-
- override fun registerListener(listener: NotifPanelEvents.Listener) {
- listeners.add(listener)
- }
-
- override fun unregisterListener(listener: NotifPanelEvents.Listener) {
- listeners.remove(listener)
- }
-
- fun changeExpandImmediate(expandImmediate: Boolean) {
- listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ffb41e5..70cbc64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Handler
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -104,13 +105,14 @@
mockContext,
mockPluginManager,
mockHandler,
- fakeDefaultProvider
+ isEnabled = true,
+ userHandle = UserHandle.USER_ALL,
+ defaultClockProvider = fakeDefaultProvider
) {
override var currentClockId: ClockId
get() = settingValue
set(value) { settingValue = value }
}
- registry.isEnabled = true
verify(mockPluginManager)
.addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 64dc956..4478039 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -69,10 +70,13 @@
TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER,
createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD))
+ // Embedded TaskFragment should be excluded when animated with Task.
+ .addChange(TRANSIT_CLOSE, FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, null /* taskInfo */)
.addChange(TRANSIT_CLOSE, 0 /* flags */,
createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD))
.addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
- .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build();
+ .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */)
+ .build();
// Check apps extraction
RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
mock(SurfaceControl.Transaction.class), null /* leashes */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index c961cec..b4a5f5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -36,7 +36,8 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -71,12 +72,12 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
- @Mock private NotifPanelEvents mNotifPanelEvents;
+ @Mock private ShadeStateEvents mShadeStateEvents;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
- @Captor private ArgumentCaptor<NotifPanelEvents.Listener> mNotifPanelEventsCallbackCaptor;
+ @Captor private ArgumentCaptor<ShadeStateEventsListener> mNotifPanelEventsCallbackCaptor;
@Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -84,7 +85,7 @@
private WakefulnessLifecycle.Observer mWakefulnessObserver;
private StatusBarStateController.StateListener mStatusBarStateListener;
- private NotifPanelEvents.Listener mNotifPanelEventsCallback;
+ private ShadeStateEvents.ShadeStateEventsListener mNotifPanelEventsCallback;
private NotifStabilityManager mNotifStabilityManager;
private NotificationEntry mEntry;
private GroupEntry mGroupEntry;
@@ -97,7 +98,7 @@
mFakeExecutor,
mDumpManager,
mHeadsUpManager,
- mNotifPanelEvents,
+ mShadeStateEvents,
mStatusBarStateController,
mVisualStabilityProvider,
mWakefulnessLifecycle);
@@ -111,7 +112,8 @@
verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
mStatusBarStateListener = mSBStateListenerCaptor.getValue();
- verify(mNotifPanelEvents).registerListener(mNotifPanelEventsCallbackCaptor.capture());
+ verify(mShadeStateEvents).addShadeStateEventsListener(
+ mNotifPanelEventsCallbackCaptor.capture());
mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue();
verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
deleted file mode 100644
index 81b8e98..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.view.NotificationHeaderView;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.internal.widget.NotificationActionListLayout;
-import com.android.internal.widget.NotificationExpandButton;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.statusbar.notification.FeedbackIcon;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class NotificationContentViewTest extends SysuiTestCase {
-
- NotificationContentView mView;
-
- @Before
- @UiThreadTest
- public void setup() {
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
-
- mView = new NotificationContentView(mContext, null);
- ExpandableNotificationRow row = new ExpandableNotificationRow(mContext, null);
- ExpandableNotificationRow mockRow = spy(row);
- doReturn(10).when(mockRow).getIntrinsicHeight();
-
- mView.setContainingNotification(mockRow);
- mView.setHeights(10, 20, 30);
-
- mView.setContractedChild(createViewWithHeight(10));
- mView.setExpandedChild(createViewWithHeight(20));
- mView.setHeadsUpChild(createViewWithHeight(30));
-
- mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- }
-
- private View createViewWithHeight(int height) {
- View view = new View(mContext, null);
- view.setMinimumHeight(height);
- return view;
- }
-
- @Test
- @UiThreadTest
- public void testSetFeedbackIcon() {
- View mockContracted = mock(NotificationHeaderView.class);
- when(mockContracted.findViewById(com.android.internal.R.id.feedback))
- .thenReturn(mockContracted);
- when(mockContracted.getContext()).thenReturn(mContext);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.findViewById(com.android.internal.R.id.feedback))
- .thenReturn(mockExpanded);
- when(mockExpanded.getContext()).thenReturn(mContext);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.findViewById(com.android.internal.R.id.feedback))
- .thenReturn(mockHeadsUp);
- when(mockHeadsUp.getContext()).thenReturn(mContext);
-
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- mView.setFeedbackIcon(new FeedbackIcon(R.drawable.ic_feedback_alerted,
- R.string.notification_feedback_indicator_alerted));
-
- verify(mockContracted, times(1)).setVisibility(View.VISIBLE);
- verify(mockExpanded, times(1)).setVisibility(View.VISIBLE);
- verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE);
- }
-
- @Test
- @UiThreadTest
- public void testExpandButtonFocusIsCalled() {
- View mockContractedEB = mock(NotificationExpandButton.class);
- View mockContracted = mock(NotificationHeaderView.class);
- when(mockContracted.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mockContracted.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
- mockContractedEB);
- when(mockContracted.getContext()).thenReturn(mContext);
-
- View mockExpandedEB = mock(NotificationExpandButton.class);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mockExpanded.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
- mockExpandedEB);
- when(mockExpanded.getContext()).thenReturn(mContext);
-
- View mockHeadsUpEB = mock(NotificationExpandButton.class);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mockHeadsUp.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
- mockHeadsUpEB);
- when(mockHeadsUp.getContext()).thenReturn(mContext);
-
- // Set up all 3 child forms
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- // This is required to call requestAccessibilityFocus()
- mView.setFocusOnVisibilityChange();
-
- // The following will initialize the view and switch from not visible to expanded.
- // (heads-up is actually an alternate form of contracted, hence this enters expanded state)
- mView.setHeadsUp(true);
-
- verify(mockContractedEB, times(0)).requestAccessibilityFocus();
- verify(mockExpandedEB, times(1)).requestAccessibilityFocus();
- verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus();
- }
-
- @Test
- @UiThreadTest
- public void testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
- View mockContracted = mock(NotificationHeaderView.class);
-
- View mockExpandedActions = mock(NotificationActionListLayout.class);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockExpandedActions);
-
- View mockHeadsUpActions = mock(NotificationActionListLayout.class);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockHeadsUpActions);
-
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- mView.setRemoteInputVisible(true);
-
- verify(mockContracted, times(0)).findViewById(0);
- verify(mockExpandedActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- }
-
- @Test
- @UiThreadTest
- public void testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
- View mockContracted = mock(NotificationHeaderView.class);
-
- View mockExpandedActions = mock(NotificationActionListLayout.class);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockExpandedActions);
-
- View mockHeadsUpActions = mock(NotificationActionListLayout.class);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockHeadsUpActions);
-
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- mView.setRemoteInputVisible(false);
-
- verify(mockContracted, times(0)).findViewById(0);
- verify(mockExpandedActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
new file mode 100644
index 0000000..562b4df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.res.Resources
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import android.view.NotificationHeaderView
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.widget.NotificationActionListLayout
+import com.android.internal.widget.NotificationExpandButton
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.statusbar.notification.FeedbackIcon
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationContentViewTest : SysuiTestCase() {
+ private lateinit var view: NotificationContentView
+
+ @Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier
+
+ private val notificationContentMargin =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_content_margin)
+
+ @Before
+ fun setup() {
+ initMocks(this)
+
+ mDependency.injectMockDependency(MediaOutputDialogFactory::class.java)
+
+ view = spy(NotificationContentView(mContext, /* attrs= */ null))
+ val row = ExpandableNotificationRow(mContext, /* attrs= */ null)
+ row.entry = createMockNotificationEntry(false)
+ val spyRow = spy(row)
+ doReturn(10).whenever(spyRow).intrinsicHeight
+
+ with(view) {
+ initialize(mPeopleNotificationIdentifier, mock(), mock(), mock())
+ setContainingNotification(spyRow)
+ setHeights(/* smallHeight= */ 10, /* headsUpMaxHeight= */ 20, /* maxHeight= */ 30)
+ contractedChild = createViewWithHeight(10)
+ expandedChild = createViewWithHeight(20)
+ headsUpChild = createViewWithHeight(30)
+ measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ layout(0, 0, view.measuredWidth, view.measuredHeight)
+ }
+ }
+
+ private fun createViewWithHeight(height: Int) =
+ View(mContext, /* attrs= */ null).apply { minimumHeight = height }
+
+ @Test
+ fun testSetFeedbackIcon() {
+ // Given: contractedChild, enpandedChild, and headsUpChild being set
+ val mockContracted = createMockNotificationHeaderView()
+ val mockExpanded = createMockNotificationHeaderView()
+ val mockHeadsUp = createMockNotificationHeaderView()
+
+ with(view) {
+ contractedChild = mockContracted
+ expandedChild = mockExpanded
+ headsUpChild = mockHeadsUp
+ }
+
+ // When: FeedBackIcon is set
+ view.setFeedbackIcon(
+ FeedbackIcon(
+ R.drawable.ic_feedback_alerted,
+ R.string.notification_feedback_indicator_alerted
+ )
+ )
+
+ // Then: contractedChild, enpandedChild, and headsUpChild should be set to be visible
+ verify(mockContracted).visibility = View.VISIBLE
+ verify(mockExpanded).visibility = View.VISIBLE
+ verify(mockHeadsUp).visibility = View.VISIBLE
+ }
+
+ private fun createMockNotificationHeaderView() =
+ mock<NotificationHeaderView>().apply {
+ whenever(this.findViewById<View>(R.id.feedback)).thenReturn(this)
+ whenever(this.context).thenReturn(mContext)
+ }
+
+ @Test
+ fun testExpandButtonFocusIsCalled() {
+ val mockContractedEB = mock<NotificationExpandButton>()
+ val mockContracted = createMockNotificationHeaderView(mockContractedEB)
+
+ val mockExpandedEB = mock<NotificationExpandButton>()
+ val mockExpanded = createMockNotificationHeaderView(mockExpandedEB)
+
+ val mockHeadsUpEB = mock<NotificationExpandButton>()
+ val mockHeadsUp = createMockNotificationHeaderView(mockHeadsUpEB)
+
+ // Set up all 3 child forms
+ view.contractedChild = mockContracted
+ view.expandedChild = mockExpanded
+ view.headsUpChild = mockHeadsUp
+
+ // This is required to call requestAccessibilityFocus()
+ view.setFocusOnVisibilityChange()
+
+ // The following will initialize the view and switch from not visible to expanded.
+ // (heads-up is actually an alternate form of contracted, hence this enters expanded state)
+ view.setHeadsUp(true)
+ verify(mockContractedEB, never()).requestAccessibilityFocus()
+ verify(mockExpandedEB).requestAccessibilityFocus()
+ verify(mockHeadsUpEB, never()).requestAccessibilityFocus()
+ }
+
+ private fun createMockNotificationHeaderView(mockExpandedEB: NotificationExpandButton) =
+ mock<NotificationHeaderView>().apply {
+ whenever(this.animate()).thenReturn(mock())
+ whenever(this.findViewById<View>(R.id.expand_button)).thenReturn(mockExpandedEB)
+ whenever(this.context).thenReturn(mContext)
+ }
+
+ @Test
+ fun testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
+ val mockContracted = mock<NotificationHeaderView>()
+
+ val mockExpandedActions = mock<NotificationActionListLayout>()
+ val mockExpanded = mock<NotificationHeaderView>()
+ whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
+
+ val mockHeadsUpActions = mock<NotificationActionListLayout>()
+ val mockHeadsUp = mock<NotificationHeaderView>()
+ whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
+
+ with(view) {
+ contractedChild = mockContracted
+ expandedChild = mockExpanded
+ headsUpChild = mockHeadsUp
+ }
+
+ view.setRemoteInputVisible(true)
+
+ verify(mockContracted, never()).findViewById<View>(0)
+ verify(mockExpandedActions).importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ verify(mockHeadsUpActions).importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ }
+
+ @Test
+ fun testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
+ val mockContracted = mock<NotificationHeaderView>()
+
+ val mockExpandedActions = mock<NotificationActionListLayout>()
+ val mockExpanded = mock<NotificationHeaderView>()
+ whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
+
+ val mockHeadsUpActions = mock<NotificationActionListLayout>()
+ val mockHeadsUp = mock<NotificationHeaderView>()
+ whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
+
+ with(view) {
+ contractedChild = mockContracted
+ expandedChild = mockExpanded
+ headsUpChild = mockHeadsUp
+ }
+
+ view.setRemoteInputVisible(false)
+
+ verify(mockContracted, never()).findViewById<View>(0)
+ verify(mockExpandedActions).importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ verify(mockHeadsUpActions).importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+
+ @Test
+ fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ // Bubble button should not be shown for the given NotificationEntry
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+
+ // When: call NotificationContentView.setExpandedChild() to set the expandedChild
+ view.expandedChild = mockExpandedChild
+
+ // Then: bottom margin of actionListMarginTarget should not change,
+ // still be notificationContentMargin
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+ }
+
+ @Test
+ fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ // Bubble button should be shown for the given NotificationEntry
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ true)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+
+ // When: call NotificationContentView.setExpandedChild() to set the expandedChild
+ view.expandedChild = mockExpandedChild
+
+ // Then: bottom margin of actionListMarginTarget should be set to 0
+ assertEquals(0, getMarginBottom(actionListMarginTarget))
+ }
+
+ @Test
+ fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+ view.expandedChild = mockExpandedChild
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+
+ // When: call NotificationContentView.onNotificationUpdated() to update the
+ // NotificationEntry, which should not show bubble button
+ view.onNotificationUpdated(createMockNotificationEntry(/* showButton= */ false))
+
+ // Then: bottom margin of actionListMarginTarget should not change, still be 20
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+ }
+
+ @Test
+ fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+ view.expandedChild = mockExpandedChild
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+
+ // When: call NotificationContentView.onNotificationUpdated() to update the
+ // NotificationEntry, which should show bubble button
+ view.onNotificationUpdated(createMockNotificationEntry(true))
+
+ // Then: bottom margin of actionListMarginTarget should not change, still be 20
+ assertEquals(0, getMarginBottom(actionListMarginTarget))
+ }
+
+ private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.entry).thenReturn(notificationEntry)
+ whenever(this.context).thenReturn(mContext)
+ whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
+ }
+
+ private fun createMockNotificationEntry(showButton: Boolean) =
+ mock<NotificationEntry>().apply {
+ whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this))
+ .thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON)
+ whenever(this.bubbleMetadata).thenReturn(mock())
+ val sbnMock: StatusBarNotification = mock()
+ val userMock: UserHandle = mock()
+ whenever(this.sbn).thenReturn(sbnMock)
+ whenever(sbnMock.user).thenReturn(userMock)
+ doReturn(showButton).whenever(view).shouldShowBubbleButton(this)
+ }
+
+ private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
+ val outerLayout = LinearLayout(mContext)
+ val innerLayout = LinearLayout(mContext)
+ outerLayout.addView(innerLayout)
+ val mlp = innerLayout.layoutParams as ViewGroup.MarginLayoutParams
+ mlp.setMargins(0, 0, 0, bottomMargin)
+ return innerLayout
+ }
+
+ private fun createMockExpandedChild(notificationEntry: NotificationEntry) =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock())
+ whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock())
+ whenever(this.entry).thenReturn(notificationEntry)
+ whenever(this.context).thenReturn(mContext)
+
+ val resourcesMock: Resources = mock()
+ whenever(resourcesMock.configuration).thenReturn(mock())
+ whenever(this.resources).thenReturn(resourcesMock)
+ }
+
+ private fun getMarginBottom(layout: LinearLayout): Int =
+ (layout.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 91aecd8..dceb4ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -78,6 +78,7 @@
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,6 +90,7 @@
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
+@Ignore("b/255552856")
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 6de8bd5..5755782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -235,6 +235,7 @@
@Mock private NavigationBarController mNavigationBarController;
@Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
@Mock private SysuiColorExtractor mColorExtractor;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock private ColorExtractor.GradientColors mGradientColors;
@Mock private PulseExpansionHandler mPulseExpansionHandler;
@Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@@ -366,10 +367,10 @@
return null;
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
- WakefulnessLifecycle wakefulnessLifecycle =
+ mWakefulnessLifecycle =
new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager);
- wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
- wakefulnessLifecycle.dispatchFinishedWakingUp();
+ mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+ mWakefulnessLifecycle.dispatchFinishedWakingUp();
when(mGradientColors.supportsDarkText()).thenReturn(true);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
@@ -428,7 +429,7 @@
mBatteryController,
mColorExtractor,
new ScreenLifecycle(mDumpManager),
- wakefulnessLifecycle,
+ mWakefulnessLifecycle,
mStatusBarStateController,
Optional.of(mBubbles),
mDeviceProvisionedController,
@@ -507,6 +508,8 @@
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
mCentralSurfaces.mBarService = mBarService;
mCentralSurfaces.mStackScroller = mStackScroller;
+ mCentralSurfaces.mGestureWakeLock = mPowerManager.newWakeLock(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
mCentralSurfaces.startKeyguard();
mInitController.executePostInitTasks();
notificationLogger.setUpWithContainer(mNotificationListContainer);
@@ -1125,6 +1128,55 @@
assertThat(onDismissActionCaptor.getValue().onDismiss()).isFalse();
}
+ @Test
+ public void testKeyguardHideDelayedIfOcclusionAnimationRunning() {
+ // Show the keyguard and verify we've done so.
+ setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.KEYGUARD);
+
+ // Request to hide the keyguard, but while the occlude animation is playing. We should delay
+ // this hide call, since we're playing the occlude animation over the keyguard and thus want
+ // it to remain visible.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController, never()).setState(StatusBarState.SHADE);
+
+ // Once the animation ends, verify that the keyguard is actually hidden.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.SHADE);
+ }
+
+ @Test
+ public void testKeyguardHideNotDelayedIfOcclusionAnimationNotRunning() {
+ // Show the keyguard and verify we've done so.
+ setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.KEYGUARD);
+
+ // Hide the keyguard while the occlusion animation is not running. Verify that we
+ // immediately hide the keyguard.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.SHADE);
+ }
+
+ /**
+ * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
+ * to reconfigure the keyguard to reflect the requested showing/occluded states.
+ */
+ private void setKeyguardShowingAndOccluded(boolean showing, boolean occluded) {
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(showing);
+ when(mKeyguardStateController.isOccluded()).thenReturn(occluded);
+
+ // If we want to show the keyguard, make sure that we think we're awake and not unlocking.
+ if (showing) {
+ when(mBiometricUnlockController.isWakeAndUnlock()).thenReturn(false);
+ mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+ }
+
+ mCentralSurfaces.updateIsKeyguard(false /* forceStateChange */);
+ }
+
private void setDeviceState(int state) {
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor =
ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4d1a52c..a5deaa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -58,6 +59,7 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -117,6 +119,7 @@
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private KeyguardViewMediator mKeyguardViewMediator;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -230,7 +233,8 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mKeyguardViewMediator);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -239,6 +243,8 @@
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
+
+ mScrimController.setLaunchingAffordanceWithPreview(false);
}
@After
@@ -852,7 +858,8 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mKeyguardViewMediator);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1592,6 +1599,30 @@
assertScrimAlpha(mScrimBehind, 0);
}
+ @Test
+ public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
+ }
+
+ @Test
+ public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+ mScrimController.setLaunchingAffordanceWithPreview(true);
+
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index fa7b259..9957c2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -14,8 +14,6 @@
import com.android.internal.view.AppearanceRegion
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.SysuiStatusBarStateController
import org.junit.Before
import org.junit.Test
@@ -40,7 +38,6 @@
@Mock private lateinit var lightBarController: LightBarController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator
- @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var centralSurfaces: CentralSurfaces
private lateinit var sysBarAttrsListener: SystemBarAttributesListener
@@ -57,7 +54,6 @@
sysBarAttrsListener =
SystemBarAttributesListener(
centralSurfaces,
- featureFlags,
letterboxAppearanceCalculator,
statusBarStateController,
lightBarController,
@@ -74,18 +70,14 @@
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToCentralSurfaces() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
}
@Test
- fun onSysBarAttrsChanged_flagTrue_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>())
verify(centralSurfaces).setAppearance(TEST_APPEARANCE)
@@ -100,9 +92,7 @@
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
verify(statusBarStateController)
@@ -120,9 +110,7 @@
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToLightBarController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToLightBarController() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
verify(lightBarController)
@@ -135,7 +123,6 @@
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToStatusBarStateController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -148,7 +135,6 @@
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -164,7 +150,6 @@
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -175,7 +160,6 @@
@Test
fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf())
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -184,17 +168,6 @@
verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
}
- @Test
- fun onStatusBarBoundsChanged_flagFalse_doesNothing() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(false)
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
- reset(centralSurfaces, lightBarController, statusBarStateController)
-
- sysBarAttrsListener.onStatusBarBoundsChanged()
-
- verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
- }
-
private fun changeSysBarAttrs(@Appearance appearance: Int) {
changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
new file mode 100644
index 0000000..6ff7b7c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileConnectionRepository : MobileConnectionRepository {
+ private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
+ override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+
+ fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
+ _subscriptionsModelFlow.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 0d15268..c88d468 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -18,11 +18,11 @@
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.settingslib.mobile.MobileMappings.Config
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+class FakeMobileConnectionsRepository : MobileConnectionsRepository {
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
@@ -30,22 +30,27 @@
MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
- private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
- override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
- return subIdFlows[subId]
- ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+ private val _defaultDataSubRatConfig = MutableStateFlow(Config())
+ override val defaultDataSubRatConfig = _defaultDataSubRatConfig
+
+ private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+ return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
}
fun setSubscriptions(subs: List<SubscriptionInfo>) {
_subscriptionsFlow.value = subs
}
+ fun setDefaultDataSubRatConfig(config: Config) {
+ _defaultDataSubRatConfig.value = config
+ }
+
fun setActiveMobileDataSubscriptionId(subId: Int) {
_activeMobileDataSubscriptionId.value = subId
}
- fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
- val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
- subscription.value = model
+ fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) {
+ subIdRepos[subId] = repo
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
new file mode 100644
index 0000000..775e6db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileConnectionRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
+
+ underTest =
+ MobileConnectionRepositoryImpl(
+ SUB_1_ID,
+ telephonyManager,
+ IMMEDIATE,
+ logger,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly_toggles() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+ callback.onServiceStateChanged(serviceState)
+ serviceState.isEmergencyOnly = false
+ callback.onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest?.isGsm).isEqualTo(true)
+ assertThat(latest?.primaryLevel).isEqualTo(1)
+ assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataActivity() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
+ callback.onDataActivity(3)
+
+ assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_carrierNetworkChange() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun subscriptionFlow_networkType_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val type = NETWORK_TYPE_UNKNOWN
+ val expected = DefaultNetworkType(type)
+
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun subscriptionFlow_networkType_updatesUsingDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = NETWORK_TYPE_LTE
+ val expected = DefaultNetworkType(type)
+ val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun subscriptionFlow_networkType_updatesUsingOverride() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val expected = OverrideNetworkType(type)
+ val ti =
+ mock<TelephonyDisplayInfo>().also {
+ whenever(it.overrideNetworkType).thenReturn(type)
+ }
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ /** Convenience constructor for SignalStrength */
+ private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.isGsm).thenReturn(isGsm)
+ whenever(signalStrength.level).thenReturn(gsmLevel)
+ val cdmaStrength =
+ mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+ whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+ .thenReturn(listOf(cdmaStrength))
+
+ return signalStrength
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
new file mode 100644
index 0000000..326e0d281
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileConnectionsRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ ArgumentMatchers.anyInt(),
+ nullable(),
+ )
+ )
+ .thenReturn(flowOf(Unit))
+
+ underTest =
+ MobileConnectionsRepositoryImpl(
+ subscriptionManager,
+ telephonyManager,
+ logger,
+ broadcastDispatcher,
+ context,
+ IMMEDIATE,
+ scope,
+ mock(),
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ // WHEN 2 networks show up
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.activeMobileDataSubscriptionId.value)
+ .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() =
+ runBlocking(IMMEDIATE) {
+ var active: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionRepository_validSubId_isCached() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1).isSameInstanceAs(repo2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionCache_clearsInvalidSubscriptions() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Get repos to trigger caching
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+ assertThat(underTest.getSubIdRepoCache())
+ .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+ // SUB_2 disappears
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionRepository_invalidSubId_throws() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.getRepoForSubId(SUB_1_ID)
+ }
+
+ job.cancel()
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
deleted file mode 100644
index 316b795..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import android.telephony.CellSignalStrengthCdma
-import android.telephony.ServiceState
-import android.telephony.SignalStrength
-import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
-import android.telephony.TelephonyCallback
-import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-class MobileSubscriptionRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: MobileSubscriptionRepositoryImpl
-
- @Mock private lateinit var subscriptionManager: SubscriptionManager
- @Mock private lateinit var telephonyManager: TelephonyManager
- private val scope = CoroutineScope(IMMEDIATE)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
- MobileSubscriptionRepositoryImpl(
- subscriptionManager,
- telephonyManager,
- IMMEDIATE,
- scope,
- )
- }
-
- @After
- fun tearDown() {
- scope.cancel()
- }
-
- @Test
- fun testSubscriptions_initiallyEmpty() =
- runBlocking(IMMEDIATE) {
- assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
- }
-
- @Test
- fun testSubscriptions_listUpdates() =
- runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
-
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
- job.cancel()
- }
-
- @Test
- fun testSubscriptions_removingSub_updatesList() =
- runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
-
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
- // WHEN 2 networks show up
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // WHEN one network is removed
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // THEN the subscriptions list represents the newest change
- assertThat(latest).isEqualTo(listOf(SUB_2))
-
- job.cancel()
- }
-
- @Test
- fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
- runBlocking(IMMEDIATE) {
- assertThat(underTest.activeMobileDataSubscriptionId.value)
- .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
- }
-
- @Test
- fun testActiveDataSubscriptionId_updates() =
- runBlocking(IMMEDIATE) {
- var active: Int? = null
-
- val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
-
- getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
- assertThat(active).isEqualTo(SUB_2_ID)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_default() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(MobileSubscriptionModel())
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_emergencyOnly() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val serviceState = ServiceState()
- serviceState.isEmergencyOnly = true
-
- getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
-
- assertThat(latest?.isEmergencyOnly).isEqualTo(true)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_emergencyOnly_toggles() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<ServiceStateListener>()
- val serviceState = ServiceState()
- serviceState.isEmergencyOnly = true
- callback.onServiceStateChanged(serviceState)
- serviceState.isEmergencyOnly = false
- callback.onServiceStateChanged(serviceState)
-
- assertThat(latest?.isEmergencyOnly).isEqualTo(false)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_signalStrengths_levelsUpdate() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
- val strength = signalStrength(1, 2, true)
- callback.onSignalStrengthsChanged(strength)
-
- assertThat(latest?.isGsm).isEqualTo(true)
- assertThat(latest?.primaryLevel).isEqualTo(1)
- assertThat(latest?.cdmaLevel).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_dataConnectionState() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
- callback.onDataConnectionStateChanged(100, 200 /* unused */)
-
- assertThat(latest?.dataConnectionState).isEqualTo(100)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_dataActivity() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<DataActivityListener>()
- callback.onDataActivity(3)
-
- assertThat(latest?.dataActivityDirection).isEqualTo(3)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_carrierNetworkChange() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
- callback.onCarrierNetworkChange(true)
-
- assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_displayInfo() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<DisplayInfoListener>()
- val ti = mock<TelephonyDisplayInfo>()
- callback.onDisplayInfoChanged(ti)
-
- assertThat(latest?.displayInfo).isEqualTo(ti)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_isCached() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- val state1 = underTest.getFlowForSubId(SUB_1_ID)
- val state2 = underTest.getFlowForSubId(SUB_1_ID)
-
- assertThat(state1).isEqualTo(state2)
- }
-
- @Test
- fun testFlowForSubId_isRemovedAfterFinish() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
-
- // Start collecting on some flow
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- // There should be once cached flow now
- assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
-
- // When the job is canceled, the cache should be cleared
- job.cancel()
-
- assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
- }
-
- private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
- val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
- verify(subscriptionManager)
- .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
- getTelephonyCallbackForType()
-
- private fun getTelephonyCallbacks(): List<TelephonyCallback> {
- val callbackCaptor = argumentCaptor<TelephonyCallback>()
- verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
- return callbackCaptor.allValues
- }
-
- private inline fun <reified T> getTelephonyCallbackForType(): T {
- val cbs = getTelephonyCallbacks().filterIsInstance<T>()
- assertThat(cbs.size).isEqualTo(1)
- return cbs[0]
- }
-
- /** Convenience constructor for SignalStrength */
- private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
- val signalStrength = mock<SignalStrength>()
- whenever(signalStrength.isGsm).thenReturn(isGsm)
- whenever(signalStrength.level).thenReturn(gsmLevel)
- val cdmaStrength =
- mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
- whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
- .thenReturn(listOf(cdmaStrength))
-
- return signalStrength
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private const val SUB_1_ID = 1
- private val SUB_1 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
-
- private const val SUB_2_ID = 2
- private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 8ec68f3..cd4dbeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -23,7 +23,7 @@
class FakeMobileIconInteractor : MobileIconInteractor {
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
- override val iconGroup = _iconGroup
+ override val networkTypeIconGroup = _iconGroup
private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
override val isEmergencyOnly = _isEmergencyOnly
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
new file mode 100644
index 0000000..2bd2286
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
+ MobileIconsInteractor {
+ val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
+ val LTE_KEY = mobileMappings.toIconKey(LTE)
+ val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
+ val FIVE_G_OVERRIDE_KEY = mobileMappings.toIconKeyOverride(FIVE_G_OVERRIDE)
+
+ /**
+ * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
+ * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
+ * the exhaustive set of icons
+ */
+ val TEST_MAPPING: Map<String, MobileIconGroup> =
+ mapOf(
+ THREE_G_KEY to TelephonyIcons.THREE_G,
+ LTE_KEY to TelephonyIcons.LTE,
+ FOUR_G_KEY to TelephonyIcons.FOUR_G,
+ FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
+ )
+
+ private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+ override val filteredSubscriptions = _filteredSubscriptions
+
+ private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
+ override val defaultMobileIconMapping = _defaultMobileIconMapping
+
+ private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
+ override val defaultMobileIconGroup = _defaultMobileIconGroup
+
+ private val _isUserSetup = MutableStateFlow(true)
+ override val isUserSetup = _isUserSetup
+
+ /** Always returns a new fake interactor */
+ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
+ return FakeMobileIconInteractor()
+ }
+
+ companion object {
+ val DEFAULT_ICON = TelephonyIcons.G
+
+ // Use [MobileMappings] to define some simple definitions
+ const val THREE_G = NETWORK_TYPE_GSM
+ const val LTE = NETWORK_TYPE_LTE
+ const val FOUR_G = NETWORK_TYPE_UMTS
+ const val FIVE_G_OVERRIDE = OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 2f07d9c..ff44af4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -18,10 +18,19 @@
import android.telephony.CellSignalStrength
import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -29,26 +38,33 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
@SmallTest
class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
- private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
- private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+ private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
+ private val connectionRepository = FakeMobileConnectionRepository()
@Before
fun setUp() {
- underTest = MobileIconInteractorImpl(sub1Flow)
+ underTest =
+ MobileIconInteractorImpl(
+ mobileIconsInteractor.defaultMobileIconMapping,
+ mobileIconsInteractor.defaultMobileIconGroup,
+ mobileMappingsProxy,
+ connectionRepository,
+ )
}
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = true),
- SUB_1_ID
)
var latest: Int? = null
@@ -62,13 +78,12 @@
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = true,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
- SUB_1_ID
)
var latest: Int? = null
@@ -82,9 +97,8 @@
@Test
fun cdma_level_default_unknown() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = false),
- SUB_1_ID
)
var latest: Int? = null
@@ -97,13 +111,12 @@
@Test
fun cdma_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = false,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
- SUB_1_ID
)
var latest: Int? = null
@@ -114,6 +127,75 @@
job.cancel()
}
+ @Test
+ fun iconGroup_three_g() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_updates_on_change() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ resolvedNetworkType = DefaultNetworkType(FOUR_G),
+ ),
+ )
+ yield()
+
+ assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_5g_override_type() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(TelephonyIcons.NR_5G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_default_if_no_lookup() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
+ ),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(FakeMobileIconsInteractor.DEFAULT_ICON)
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
@@ -123,9 +205,5 @@
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
-
- private const val SUB_2_ID = 2
- private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 89ad9cb..b01efd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -19,12 +19,14 @@
import android.telephony.SubscriptionInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -39,7 +41,9 @@
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
private val userSetupRepository = FakeUserSetupRepository()
- private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+ private val subscriptionsRepository = FakeMobileConnectionsRepository()
+ private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -47,10 +51,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest =
- MobileIconsInteractor(
+ MobileIconsInteractorImpl(
subscriptionsRepository,
carrierConfigTracker,
+ mobileMappingsProxy,
userSetupRepository,
+ scope
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
new file mode 100644
index 0000000..6d8d902
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.util
+
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.settingslib.mobile.TelephonyIcons
+
+class FakeMobileMappingsProxy : MobileMappingsProxy {
+ private var iconMap = mapOf<String, MobileIconGroup>()
+ private var defaultIcons = TelephonyIcons.THREE_G
+
+ fun setIconMap(map: Map<String, MobileIconGroup>) {
+ iconMap = map
+ }
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap
+ fun getIconMap() = iconMap
+
+ fun setDefaultIcons(group: MobileIconGroup) {
+ defaultIcons = group
+ }
+ override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons
+ fun getDefaultIcons(): MobileIconGroup = defaultIcons
+
+ override fun toIconKey(networkType: Int): String {
+ return networkType.toString()
+ }
+
+ override fun toIconKeyOverride(networkType: Int): String {
+ return toIconKey(networkType) + "_override"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index b68eb88..91b5c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -41,6 +41,7 @@
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -85,10 +86,29 @@
}
@Test
- fun displayView_viewAdded() {
- underTest.displayView(getState())
+ fun displayView_viewAddedWithCorrectTitle() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
+ )
+ )
- verify(windowManager).addView(any(), any())
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(any(), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value!!.title).isEqualTo("Fake Window Title")
+ }
+
+ @Test
+ fun displayView_logged() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
+ )
+ )
+
+ verify(logger).logViewAddition("Fake Window Title")
}
@Test
@@ -110,7 +130,7 @@
}
@Test
- fun displayView_twice_viewNotAddedTwice() {
+ fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
underTest.displayView(getState())
reset(windowManager)
@@ -119,6 +139,32 @@
}
@Test
+ fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "First Fake Window Title",
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Second Fake Window Title",
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+
+ verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+
+ assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
+ assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
+ verify(windowManager).removeView(viewCaptor.allValues[0])
+ }
+
+ @Test
fun displayView_viewDoesNotDisappearsBeforeTimeout() {
val state = getState()
underTest.displayView(state)
@@ -197,7 +243,7 @@
underTest.removeView(reason)
verify(windowManager).removeView(any())
- verify(logger).logChipRemoval(reason)
+ verify(logger).logViewRemoval(reason)
}
@Test
@@ -232,8 +278,6 @@
configurationController,
powerManager,
R.layout.chipbar,
- "Window Title",
- "WAKE_REASON",
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -250,9 +294,12 @@
}
}
- inner class ViewInfo(val name: String) : TemporaryViewInfo {
- override fun getTimeoutMs() = 1L
- }
+ inner class ViewInfo(
+ val name: String,
+ override val windowTitle: String = "Window Title",
+ override val wakeReason: String = "WAKE_REASON",
+ override val timeoutMs: Int = 1
+ ) : TemporaryViewInfo()
}
private const val TIMEOUT_MS = 10000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 13e9f60..d155050 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -43,20 +43,21 @@
}
@Test
- fun logChipAddition_bufferHasLog() {
- logger.logChipAddition()
+ fun logViewAddition_bufferHasLog() {
+ logger.logViewAddition("Test Window Title")
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains("Test Window Title")
}
@Test
- fun logChipRemoval_bufferHasTagAndReason() {
+ fun logViewRemoval_bufferHasTagAndReason() {
val reason = "test reason"
- logger.logChipRemoval(reason)
+ logger.logViewRemoval(reason)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 9fbf159..f643973 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -35,12 +35,12 @@
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -60,7 +60,7 @@
class ChipbarCoordinatorTest : SysuiTestCase() {
private lateinit var underTest: FakeChipbarCoordinator
- @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var logger: ChipbarLogger
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var powerManager: PowerManager
@@ -105,7 +105,7 @@
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("text"),
endItem = null,
@@ -121,7 +121,7 @@
fun displayView_resourceIcon_correctlyRendered() {
val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.drawable.ic_cake, contentDescription),
Text.Loaded("text"),
endItem = null,
@@ -136,7 +136,7 @@
@Test
fun displayView_loadedText_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("display view text here"),
endItem = null,
@@ -149,7 +149,7 @@
@Test
fun displayView_resourceText_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Resource(R.string.screenrecord_start_error),
endItem = null,
@@ -163,7 +163,7 @@
@Test
fun displayView_endItemNull_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -179,7 +179,7 @@
@Test
fun displayView_endItemLoading_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Loading,
@@ -195,7 +195,7 @@
@Test
fun displayView_endItemError_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Error,
@@ -211,7 +211,7 @@
@Test
fun displayView_endItemButton_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -237,7 +237,7 @@
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -260,7 +260,7 @@
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -279,7 +279,7 @@
@Test
fun displayView_vibrationEffect_doubleClickEffect() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -296,7 +296,7 @@
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("title text"),
endItem = ChipbarEndItem.Loading,
@@ -314,7 +314,7 @@
// WHEN the view is updated
val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
underTest.updateView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
Text.Loaded("new title text"),
endItem = ChipbarEndItem.Error,
@@ -331,6 +331,47 @@
assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
+ @Test
+ fun viewUpdates_logged() {
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("title text"), any())
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Loaded(drawable, ContentDescription.Loaded("new CD")),
+ Text.Loaded("new title text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
+
+ verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
+ }
+
+ private fun createChipbarInfo(
+ startIcon: Icon,
+ text: Text,
+ endItem: ChipbarEndItem?,
+ vibrationEffect: VibrationEffect? = null,
+ ): ChipbarInfo {
+ return ChipbarInfo(
+ startIcon,
+ text,
+ endItem,
+ vibrationEffect,
+ windowTitle = WINDOW_TITLE,
+ wakeReason = WAKE_REASON,
+ timeoutMs = TIMEOUT,
+ )
+ }
+
private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
private fun ViewGroup.getChipText(): String =
@@ -350,3 +391,5 @@
}
private const val TIMEOUT = 10000
+private const val WINDOW_TITLE = "Test Chipbar Window Title"
+private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 17d4023..574f70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -22,8 +22,6 @@
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -33,7 +31,7 @@
/** A fake implementation of [ChipbarCoordinator] for testing. */
class FakeChipbarCoordinator(
context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
+ logger: ChipbarLogger,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index e18dd3a..7d5f06c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -140,6 +140,40 @@
}
@Test
+ fun testOnUnfold_hingeAngleDecreasesBeforeInnerScreenAvailable_emitsOnlyStartAndInnerScreenAvailableEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ sendHingeAngleEvent(10)
+ sendHingeAngleEvent(20)
+ sendHingeAngleEvent(10)
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
+ }
+
+ @Test
+ fun testOnUnfold_hingeAngleDecreasesAfterInnerScreenAvailable_emitsStartInnerScreenAvailableAndStartClosingEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ sendHingeAngleEvent(10)
+ sendHingeAngleEvent(20)
+ screenOnStatusProvider.notifyScreenTurnedOn()
+ sendHingeAngleEvent(30)
+ sendHingeAngleEvent(40)
+ sendHingeAngleEvent(10)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
fun testOnFolded_stopsHingeAngleProvider() {
setFoldState(folded = true)
@@ -237,7 +271,7 @@
}
@Test
- fun startClosingEvent_afterTimeout_abortEmitted() {
+ fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() {
sendHingeAngleEvent(90)
sendHingeAngleEvent(80)
@@ -269,7 +303,7 @@
}
@Test
- fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
+ fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() {
sendHingeAngleEvent(180)
sendHingeAngleEvent(90)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
similarity index 77%
rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
index 76bff1d..7e8ffeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
@@ -54,7 +54,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class WallpaperColorExtractorTest extends SysuiTestCase {
+public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
private static final int LOW_BMP_WIDTH = 128;
private static final int LOW_BMP_HEIGHT = 128;
private static final int HIGH_BMP_WIDTH = 3000;
@@ -105,11 +105,11 @@
return bitmap;
}
- private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+ private WallpaperLocalColorExtractor getSpyWallpaperLocalColorExtractor() {
- WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+ WallpaperLocalColorExtractor colorExtractor = new WallpaperLocalColorExtractor(
mBackgroundExecutor,
- new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -132,25 +132,25 @@
mDeactivatedCount++;
}
});
- WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+ WallpaperLocalColorExtractor spyColorExtractor = spy(colorExtractor);
doAnswer(invocation -> {
mMiniBitmapWidth = invocation.getArgument(1);
mMiniBitmapHeight = invocation.getArgument(2);
return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
- }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+ }).when(spyColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
doAnswer(invocation -> getMockBitmap(
invocation.getArgument(1),
invocation.getArgument(2)))
- .when(spyWallpaperColorExtractor)
+ .when(spyColorExtractor)
.createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
- .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
- return spyWallpaperColorExtractor;
+ return spyColorExtractor;
}
private RectF randomArea() {
@@ -180,18 +180,18 @@
*/
@Test
public void testMiniBitmapCreation() {
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
Bitmap bitmap = getMockBitmap(width, height);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
- .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ .isAtMost(WallpaperLocalColorExtractor.SMALL_SIDE);
}
}
@@ -201,18 +201,18 @@
*/
@Test
public void testSmallMiniBitmapCreation() {
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
Bitmap bitmap = getMockBitmap(width, height);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
- .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ .isAtMost(WallpaperLocalColorExtractor.SMALL_SIDE);
}
}
@@ -228,15 +228,15 @@
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
List<Runnable> tasks = Arrays.asList(
- () -> spyWallpaperColorExtractor.onPageChanged(nPages),
- () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
- () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ () -> spyColorExtractor.onPageChanged(nPages),
+ () -> spyColorExtractor.onBitmapChanged(bitmap),
+ () -> spyColorExtractor.setDisplayDimensions(
DISPLAY_WIDTH, DISPLAY_HEIGHT),
- () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+ () -> spyColorExtractor.addLocalColorsAreas(
regions));
Collections.shuffle(tasks);
tasks.forEach(Runnable::run);
@@ -245,7 +245,7 @@
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(mColorsProcessed).isEqualTo(regions.size());
- spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
}
@@ -260,7 +260,7 @@
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions = new ArrayList<>();
@@ -268,20 +268,20 @@
regions.addAll(regions2);
int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
List<Runnable> tasks = Arrays.asList(
- () -> spyWallpaperColorExtractor.onPageChanged(nPages),
- () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
- () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ () -> spyColorExtractor.onPageChanged(nPages),
+ () -> spyColorExtractor.onBitmapChanged(bitmap),
+ () -> spyColorExtractor.setDisplayDimensions(
DISPLAY_WIDTH, DISPLAY_HEIGHT),
- () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+ () -> spyColorExtractor.removeLocalColorAreas(regions1));
- spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ spyColorExtractor.addLocalColorsAreas(regions);
assertThat(mActivatedCount).isEqualTo(1);
Collections.shuffle(tasks);
tasks.forEach(Runnable::run);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(mDeactivatedCount).isEqualTo(0);
- spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ spyColorExtractor.removeLocalColorAreas(regions2);
assertThat(mDeactivatedCount).isEqualTo(1);
}
}
@@ -295,18 +295,18 @@
@Test
public void testRecomputeColorExtraction() {
Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions = new ArrayList<>();
regions.addAll(regions1);
regions.addAll(regions2);
- spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ spyColorExtractor.addLocalColorsAreas(regions);
assertThat(mActivatedCount).isEqualTo(1);
int nPages = PAGES_LOW;
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
- spyWallpaperColorExtractor.onPageChanged(nPages);
- spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+ spyColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onPageChanged(nPages);
+ spyColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
int nSimulations = 20;
for (int i = 0; i < nSimulations; i++) {
@@ -315,22 +315,22 @@
// verify that if we remove some regions, they are not recomputed after other changes
if (i == nSimulations / 2) {
regions.removeAll(regions2);
- spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ spyColorExtractor.removeLocalColorAreas(regions2);
}
if (Math.random() >= 0.5) {
int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
if (nPagesNew == nPages) continue;
nPages = nPagesNew;
- spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+ spyColorExtractor.onPageChanged(nPagesNew);
} else {
Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
- spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+ spyColorExtractor.onBitmapChanged(newBitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
}
assertThat(mColorsProcessed).isEqualTo(regions.size());
}
- spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
@@ -339,12 +339,12 @@
resetCounters();
Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
doNothing().when(bitmap).recycle();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
- spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onPageChanged(PAGES_LOW);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
- spyWallpaperColorExtractor.cleanUp();
- spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+ spyColorExtractor.cleanUp();
+ spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
assertThat(mColorsProcessed).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 7af66f6..7ae47b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -36,7 +37,6 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -78,18 +78,31 @@
@Mock ProtoTracer mProtoTracer;
@Mock UserTracker mUserTracker;
@Mock ShellExecutor mSysUiMainExecutor;
- @Mock FloatingTasks mFloatingTasks;
+ @Mock NoteTaskInitializer mNoteTaskInitializer;
@Mock DesktopMode mDesktopMode;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
- Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks),
+ mWMShell = new WMShell(
+ mContext,
+ mShellInterface,
+ Optional.of(mPip),
+ Optional.of(mSplitScreen),
+ Optional.of(mOneHanded),
Optional.of(mDesktopMode),
- mCommandQueue, mConfigurationController, mKeyguardStateController,
- mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer,
- mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor);
+ mCommandQueue,
+ mConfigurationController,
+ mKeyguardStateController,
+ mKeyguardUpdateMonitor,
+ mScreenLifecycle,
+ mSysUiState,
+ mProtoTracer,
+ mWakefulnessLifecycle,
+ mUserTracker,
+ mNoteTaskInitializer,
+ mSysUiMainExecutor
+ );
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 8d171be..69575a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -26,7 +26,9 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatcher
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
import org.mockito.stubbing.OngoingStubbing
+import org.mockito.stubbing.Stubber
/**
* Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
@@ -89,7 +91,8 @@
*
* @see Mockito.when
*/
-fun <T> whenever(methodCall: T): OngoingStubbing<T> = Mockito.`when`(methodCall)
+fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+fun <T> Stubber.whenever(mock: T): T = `when`(mock)
/**
* A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 043aff6..b568186 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.unfold.progress
+import android.os.Trace
import android.util.Log
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -117,6 +118,7 @@
if (DEBUG) {
Log.d(TAG, "onFoldUpdate = $update")
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "fold_update", update)
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 07473b3..808128d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.updates
import android.os.Handler
+import android.os.Trace
import android.util.Log
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
@@ -108,6 +109,7 @@
private fun onHingeAngle(angle: Float) {
if (DEBUG) {
Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle")
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "hinge_angle", angle.toInt())
}
val isClosing = angle < lastHingeAngle
@@ -115,8 +117,16 @@
val closingThresholdMet = closingThreshold == null || angle < closingThreshold
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+ val screenAvailableEventSent = isUnfoldHandled
- if (isClosing && closingThresholdMet && !closingEventDispatched && !isFullyOpened) {
+ if (isClosing // hinge angle should be decreasing since last update
+ && closingThresholdMet // hinge angle is below certain threshold
+ && !closingEventDispatched // we haven't sent closing event already
+ && !isFullyOpened // do not send closing event if we are in fully opened hinge
+ // angle range as closing threshold could overlap this range
+ && screenAvailableEventSent // do not send closing event if we are still in
+ // the process of turning on the inner display
+ ) {
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
}
diff --git a/proto/src/task_snapshot.proto b/proto/src/task_snapshot.proto
deleted file mode 100644
index 1cbc17e..0000000
--- a/proto/src/task_snapshot.proto
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
- syntax = "proto3";
-
- package com.android.server.wm;
-
- option java_package = "com.android.server.wm";
- option java_outer_classname = "WindowManagerProtos";
-
- message TaskSnapshotProto {
- int32 orientation = 1;
- int32 inset_left = 2;
- int32 inset_top = 3;
- int32 inset_right = 4;
- int32 inset_bottom = 5;
- bool is_real_snapshot = 6;
- int32 windowing_mode = 7;
- int32 system_ui_visibility = 8 [deprecated=true];
- bool is_translucent = 9;
- string top_activity_component = 10;
- // deprecated because original width and height are stored now instead of the scale.
- float legacy_scale = 11 [deprecated=true];
- int64 id = 12;
- int32 rotation = 13;
- // The task width when the snapshot was taken
- int32 task_width = 14;
- // The task height when the snapshot was taken
- int32 task_height = 15;
- int32 appearance = 16;
- int32 letterbox_inset_left = 17;
- int32 letterbox_inset_top = 18;
- int32 letterbox_inset_right = 19;
- int32 letterbox_inset_bottom = 20;
- }
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
new file mode 100644
index 0000000..f26404c6
--- /dev/null
+++ b/proto/src/windowmanager.proto
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package com.android.server.wm;
+
+option java_package = "com.android.server.wm";
+option java_outer_classname = "WindowManagerProtos";
+
+message TaskSnapshotProto {
+ int32 orientation = 1;
+ int32 inset_left = 2;
+ int32 inset_top = 3;
+ int32 inset_right = 4;
+ int32 inset_bottom = 5;
+ bool is_real_snapshot = 6;
+ int32 windowing_mode = 7;
+ int32 system_ui_visibility = 8 [deprecated=true];
+ bool is_translucent = 9;
+ string top_activity_component = 10;
+ // deprecated because original width and height are stored now instead of the scale.
+ float legacy_scale = 11 [deprecated=true];
+ int64 id = 12;
+ int32 rotation = 13;
+ // The task width when the snapshot was taken
+ int32 task_width = 14;
+ // The task height when the snapshot was taken
+ int32 task_height = 15;
+ int32 appearance = 16;
+ int32 letterbox_inset_left = 17;
+ int32 letterbox_inset_top = 18;
+ int32 letterbox_inset_right = 19;
+ int32 letterbox_inset_bottom = 20;
+}
+
+// Persistent letterboxing configurations
+message LetterboxProto {
+
+ // Possible values for the letterbox horizontal reachability
+ enum LetterboxHorizontalReachability {
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT = 0;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT = 2;
+ }
+
+ // Possible values for the letterbox vertical reachability
+ enum LetterboxVerticalReachability {
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP = 0;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;
+ }
+
+ // Represents the current horizontal position for the letterboxed activity
+ LetterboxHorizontalReachability letterbox_position_for_horizontal_reachability = 1;
+ // Represents the current vertical position for the letterboxed activity
+ LetterboxVerticalReachability letterbox_position_for_vertical_reachability = 2;
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 15c569e..4013ace 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -117,6 +117,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
/**
* Service to manage game related features.
@@ -333,7 +334,7 @@
removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
final int userId = (int) msg.obj;
final String[] packageNames = getInstalledGamePackageNames(userId);
- updateConfigsForUser(userId, packageNames);
+ updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
break;
}
case SET_GAME_STATE: {
@@ -402,7 +403,8 @@
@Override
public void onPropertiesChanged(Properties properties) {
final String[] packageNames = properties.getKeyset().toArray(new String[0]);
- updateConfigsForUser(ActivityManager.getCurrentUser(), packageNames);
+ updateConfigsForUser(ActivityManager.getCurrentUser(), true /*checkGamePackage*/,
+ packageNames);
}
@Override
@@ -553,16 +555,23 @@
private static final String GAME_MODE_CONFIG_NODE_NAME = "game-mode-config";
private final String mPackageName;
- private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
+ private final Object mModeConfigLock = new Object();
+ @GuardedBy("mModeConfigLock")
+ private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs = new ArrayMap<>();
+ // if adding new properties or make any of the below overridable, the method
+ // copyAndApplyOverride should be updated accordingly
private boolean mPerfModeOptedIn = false;
private boolean mBatteryModeOptedIn = false;
private boolean mAllowDownscale = true;
private boolean mAllowAngle = true;
private boolean mAllowFpsOverride = true;
+ GamePackageConfiguration(String packageName) {
+ mPackageName = packageName;
+ }
+
GamePackageConfiguration(String packageName, int userId) {
mPackageName = packageName;
- mModeConfigs = new ArrayMap<>();
try {
final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
@@ -646,6 +655,13 @@
return xmlFound;
}
+ GameModeConfiguration getOrAddDefaultGameModeConfiguration(int gameMode) {
+ synchronized (mModeConfigLock) {
+ mModeConfigs.putIfAbsent(gameMode, new GameModeConfiguration(gameMode));
+ return mModeConfigs.get(gameMode);
+ }
+ }
+
/**
* GameModeConfiguration contains all the values for all the interventions associated with
* a game mode.
@@ -658,15 +674,23 @@
public static final String FPS_KEY = "fps";
public static final String DEFAULT_SCALING = "1.0";
public static final String DEFAULT_FPS = "";
+ public static final boolean DEFAULT_USE_ANGLE = false;
+ public static final int DEFAULT_LOADING_BOOST_DURATION = -1;
public static final String ANGLE_KEY = "useAngle";
public static final String LOADING_BOOST_KEY = "loadingBoost";
private final @GameMode int mGameMode;
- private String mScaling;
- private String mFps;
+ private String mScaling = DEFAULT_SCALING;
+ private String mFps = DEFAULT_FPS;
private final boolean mUseAngle;
private final int mLoadingBoostDuration;
+ GameModeConfiguration(int gameMode) {
+ mGameMode = gameMode;
+ mUseAngle = DEFAULT_USE_ANGLE;
+ mLoadingBoostDuration = DEFAULT_LOADING_BOOST_DURATION;
+ }
+
GameModeConfiguration(KeyValueListParser parser) {
mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
// isGameModeOptedIn() returns if an app will handle all of the changes necessary
@@ -693,11 +717,11 @@
return mGameMode;
}
- public String getScaling() {
+ public synchronized String getScaling() {
return mScaling;
}
- public int getFps() {
+ public synchronized int getFps() {
return GameManagerService.getFpsInt(mFps);
}
@@ -709,15 +733,15 @@
return mLoadingBoostDuration;
}
- public void setScaling(String scaling) {
+ public synchronized void setScaling(String scaling) {
mScaling = scaling;
}
- public void setFpsStr(String fpsStr) {
+ public synchronized void setFpsStr(String fpsStr) {
mFps = fpsStr;
}
- public boolean isValid() {
+ public boolean isActive() {
return (mGameMode == GameManager.GAME_MODE_STANDARD
|| mGameMode == GameManager.GAME_MODE_PERFORMANCE
|| mGameMode == GameManager.GAME_MODE_BATTERY)
@@ -760,8 +784,10 @@
private int getAvailableGameModesBitfield() {
int field = 0;
- for (final int mode : mModeConfigs.keySet()) {
- field |= modeToBitmask(mode);
+ synchronized (mModeConfigLock) {
+ for (final int mode : mModeConfigs.keySet()) {
+ field |= modeToBitmask(mode);
+ }
}
if (mBatteryModeOptedIn) {
field |= modeToBitmask(GameManager.GAME_MODE_BATTERY);
@@ -802,27 +828,71 @@
* @return The package's GameModeConfiguration for the provided mode or null if absent
*/
public GameModeConfiguration getGameModeConfiguration(@GameMode int gameMode) {
- return mModeConfigs.get(gameMode);
+ synchronized (mModeConfigLock) {
+ return mModeConfigs.get(gameMode);
+ }
}
/**
* Insert a new GameModeConfiguration
*/
public void addModeConfig(GameModeConfiguration config) {
- if (config.isValid()) {
- mModeConfigs.put(config.getGameMode(), config);
+ if (config.isActive()) {
+ synchronized (mModeConfigLock) {
+ mModeConfigs.put(config.getGameMode(), config);
+ }
} else {
- Slog.w(TAG, "Invalid game mode config for "
+ Slog.w(TAG, "Attempt to add inactive game mode config for "
+ mPackageName + ":" + config.toString());
}
}
- public boolean isValid() {
- return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
+ public boolean isActive() {
+ synchronized (mModeConfigLock) {
+ return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
+ }
+ }
+
+ GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
+ GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
+ // if a game mode is overridden, we treat it with the highest priority and reset any
+ // opt-in game modes so that interventions are always executed.
+ copy.mPerfModeOptedIn = mPerfModeOptedIn && !(overrideConfig != null
+ && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
+ != null);
+ copy.mBatteryModeOptedIn = mBatteryModeOptedIn && !(overrideConfig != null
+ && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
+ != null);
+
+ // if any game mode is overridden, we will consider all interventions forced-active,
+ // this can be done more granular by checking if a specific intervention is
+ // overridden under each game mode override, but only if necessary.
+ copy.mAllowDownscale = mAllowDownscale || overrideConfig != null;
+ copy.mAllowAngle = mAllowAngle || overrideConfig != null;
+ copy.mAllowFpsOverride = mAllowFpsOverride || overrideConfig != null;
+ if (overrideConfig != null) {
+ synchronized (copy.mModeConfigLock) {
+ synchronized (mModeConfigLock) {
+ for (Map.Entry<Integer, GameModeConfiguration> entry :
+ mModeConfigs.entrySet()) {
+ copy.mModeConfigs.put(entry.getKey(), entry.getValue());
+ }
+ }
+ synchronized (overrideConfig.mModeConfigLock) {
+ for (Map.Entry<Integer, GameModeConfiguration> entry :
+ overrideConfig.mModeConfigs.entrySet()) {
+ copy.mModeConfigs.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+ return copy;
}
public String toString() {
- return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]";
+ synchronized (mModeConfigLock) {
+ return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]";
+ }
}
}
@@ -893,15 +963,7 @@
}
private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
- GamePackageConfiguration config = null;
- synchronized (mOverrideConfigLock) {
- config = mOverrideConfigs.get(packageName);
- }
- if (config == null) {
- synchronized (mDeviceConfigLock) {
- config = mConfigs.get(packageName);
- }
- }
+ final GamePackageConfiguration config = getConfig(packageName);
if (config == null) {
return new int[]{};
}
@@ -1054,19 +1116,19 @@
if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
return false;
}
-
+ final GamePackageConfiguration config;
synchronized (mDeviceConfigLock) {
- final GamePackageConfiguration config = mConfigs.get(packageName);
+ config = mConfigs.get(packageName);
if (config == null) {
return false;
}
- GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
- config.getGameModeConfiguration(gameMode);
- if (gameModeConfiguration == null) {
- return false;
- }
- return gameModeConfiguration.getUseAngle();
}
+ GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
+ config.getGameModeConfiguration(gameMode);
+ if (gameModeConfiguration == null) {
+ return false;
+ }
+ return gameModeConfiguration.getUseAngle();
}
/**
@@ -1081,19 +1143,19 @@
if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
return -1;
}
-
+ final GamePackageConfiguration config;
synchronized (mDeviceConfigLock) {
- final GamePackageConfiguration config = mConfigs.get(packageName);
- if (config == null) {
- return -1;
- }
- GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
- config.getGameModeConfiguration(gameMode);
- if (gameModeConfiguration == null) {
- return -1;
- }
- return gameModeConfiguration.getLoadingBoostDuration();
+ config = mConfigs.get(packageName);
}
+ if (config == null) {
+ return -1;
+ }
+ GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
+ config.getGameModeConfiguration(gameMode);
+ if (gameModeConfiguration == null) {
+ return -1;
+ }
+ return gameModeConfiguration.getLoadingBoostDuration();
}
/**
@@ -1262,7 +1324,7 @@
try {
final float fps = 0.0f;
final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
- nativeSetOverrideFrameRate(uid, fps);
+ setOverrideFrameRate(uid, fps);
} catch (PackageManager.NameNotFoundException e) {
return;
}
@@ -1348,7 +1410,7 @@
try {
final float fps = modeConfig.getFps();
final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
- nativeSetOverrideFrameRate(uid, fps);
+ setOverrideFrameRate(uid, fps);
} catch (PackageManager.NameNotFoundException e) {
return;
}
@@ -1357,32 +1419,17 @@
private void updateInterventions(String packageName,
@GameMode int gameMode, @UserIdInt int userId) {
+ final GamePackageConfiguration packageConfig = getConfig(packageName);
if (gameMode == GameManager.GAME_MODE_STANDARD
- || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+ || gameMode == GameManager.GAME_MODE_UNSUPPORTED || packageConfig == null
+ || packageConfig.willGamePerformOptimizations(gameMode)) {
disableCompatScale(packageName);
resetFps(packageName, userId);
- return;
- }
- GamePackageConfiguration packageConfig = null;
-
- synchronized (mOverrideConfigLock) {
- packageConfig = mOverrideConfigs.get(packageName);
- }
-
- if (packageConfig == null) {
- synchronized (mDeviceConfigLock) {
- packageConfig = mConfigs.get(packageName);
+ if (packageConfig == null) {
+ Slog.v(TAG, "Package configuration not found for " + packageName);
+ return;
}
}
-
- if (packageConfig == null) {
- disableCompatScale(packageName);
- Slog.v(TAG, "Package configuration not found for " + packageName);
- return;
- }
- if (packageConfig.willGamePerformOptimizations(gameMode)) {
- return;
- }
updateCompatModeDownscale(packageConfig, packageName, gameMode);
updateFps(packageConfig, packageName, gameMode, userId);
updateUseAngle(packageName, gameMode);
@@ -1403,34 +1450,34 @@
}
}
// Adding override game mode configuration of the given package name
+ GamePackageConfiguration overrideConfig;
synchronized (mOverrideConfigLock) {
// look for the existing override GamePackageConfiguration
- GamePackageConfiguration overrideConfig = mOverrideConfigs.get(packageName);
+ overrideConfig = mOverrideConfigs.get(packageName);
if (overrideConfig == null) {
- overrideConfig = new GamePackageConfiguration(packageName, userId);
+ overrideConfig = new GamePackageConfiguration(packageName);
mOverrideConfigs.put(packageName, overrideConfig);
}
-
- // modify GameModeConfiguration intervention settings
- GamePackageConfiguration.GameModeConfiguration overrideModeConfig =
- overrideConfig.getGameModeConfiguration(gameMode);
-
- if (fpsStr != null) {
- overrideModeConfig.setFpsStr(fpsStr);
- } else {
- overrideModeConfig.setFpsStr(
- GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS);
- }
- if (scaling != null) {
- overrideModeConfig.setScaling(scaling);
- } else {
- overrideModeConfig.setScaling(
- GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING);
- }
- Slog.i(TAG, "Package Name: " + packageName
- + " FPS: " + String.valueOf(overrideModeConfig.getFps())
- + " Scaling: " + overrideModeConfig.getScaling());
}
+ // modify GameModeConfiguration intervention settings
+ GamePackageConfiguration.GameModeConfiguration overrideModeConfig =
+ overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode);
+
+ if (fpsStr != null) {
+ overrideModeConfig.setFpsStr(fpsStr);
+ } else {
+ overrideModeConfig.setFpsStr(
+ GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS);
+ }
+ if (scaling != null) {
+ overrideModeConfig.setScaling(scaling);
+ } else {
+ overrideModeConfig.setScaling(
+ GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING);
+ }
+ Slog.i(TAG, "Package Name: " + packageName
+ + " FPS: " + String.valueOf(overrideModeConfig.getFps())
+ + " Scaling: " + overrideModeConfig.getScaling());
setGameMode(packageName, gameMode, userId);
}
@@ -1496,15 +1543,7 @@
// If not, set the game mode to standard
int gameMode = getGameMode(packageName, userId);
- GamePackageConfiguration config = null;
- synchronized (mOverrideConfigLock) {
- config = mOverrideConfigs.get(packageName);
- }
- if (config == null) {
- synchronized (mDeviceConfigLock) {
- config = mConfigs.get(packageName);
- }
- }
+ final GamePackageConfiguration config = getConfig(packageName);
final int newGameMode = getNewGameMode(gameMode, config);
if (gameMode != newGameMode) {
setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
@@ -1543,18 +1582,8 @@
* Returns the string listing all the interventions currently set to a game.
*/
public String getInterventionList(String packageName) {
- GamePackageConfiguration packageConfig = null;
- synchronized (mOverrideConfigLock) {
- packageConfig = mOverrideConfigs.get(packageName);
- }
-
- if (packageConfig == null) {
- synchronized (mDeviceConfigLock) {
- packageConfig = mConfigs.get(packageName);
- }
- }
-
- StringBuilder listStrSb = new StringBuilder();
+ final GamePackageConfiguration packageConfig = getConfig(packageName);
+ final StringBuilder listStrSb = new StringBuilder();
if (packageConfig == null) {
listStrSb.append("\n No intervention found for package ")
.append(packageName);
@@ -1569,20 +1598,27 @@
* @hide
*/
@VisibleForTesting
- void updateConfigsForUser(@UserIdInt int userId, String... packageNames) {
+ void updateConfigsForUser(@UserIdInt int userId, boolean checkGamePackage,
+ String... packageNames) {
+ if (checkGamePackage) {
+ packageNames = Arrays.stream(packageNames).filter(
+ p -> isPackageGame(p, userId)).toArray(String[]::new);
+ }
try {
synchronized (mDeviceConfigLock) {
for (final String packageName : packageNames) {
final GamePackageConfiguration config =
new GamePackageConfiguration(packageName, userId);
- if (config.isValid()) {
+ if (config.isActive()) {
if (DEBUG) {
Slog.i(TAG, "Adding config: " + config.toString());
}
mConfigs.put(packageName, config);
} else {
- Slog.w(TAG, "Invalid package config for "
- + config.getPackageName() + ":" + config.toString());
+ if (DEBUG) {
+ Slog.w(TAG, "Inactive package config for "
+ + config.getPackageName() + ":" + config.toString());
+ }
mConfigs.remove(packageName);
}
}
@@ -1721,16 +1757,18 @@
*/
@VisibleForTesting
public GamePackageConfiguration getConfig(String packageName) {
- GamePackageConfiguration packageConfig = null;
+ GamePackageConfiguration overrideConfig = null;
+ GamePackageConfiguration config;
+ synchronized (mDeviceConfigLock) {
+ config = mConfigs.get(packageName);
+ }
synchronized (mOverrideConfigLock) {
- packageConfig = mOverrideConfigs.get(packageName);
+ overrideConfig = mOverrideConfigs.get(packageName);
}
- if (packageConfig == null) {
- synchronized (mDeviceConfigLock) {
- packageConfig = mConfigs.get(packageName);
- }
+ if (overrideConfig == null || config == null) {
+ return overrideConfig == null ? config : overrideConfig;
}
- return packageConfig;
+ return config.copyAndApplyOverride(overrideConfig);
}
private void registerPackageReceiver() {
@@ -1760,7 +1798,7 @@
}
switch (intent.getAction()) {
case ACTION_PACKAGE_ADDED:
- updateConfigsForUser(userId, packageName);
+ updateConfigsForUser(userId, true /*checkGamePackage*/, packageName);
break;
case ACTION_PACKAGE_REMOVED:
disableCompatScale(packageName);
@@ -1834,6 +1872,11 @@
return handlerThread;
}
+ @VisibleForTesting
+ void setOverrideFrameRate(int uid, float frameRate) {
+ nativeSetOverrideFrameRate(uid, frameRate);
+ }
+
/**
* load dynamic library for frame rate overriding JNI calls
*/
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 745555c..d2ba9c6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4018,7 +4018,7 @@
}
}
- private void setLeAudioVolumeOnModeUpdate(int mode, int streamType, int device) {
+ private void setLeAudioVolumeOnModeUpdate(int mode, int device) {
switch (mode) {
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
@@ -4032,10 +4032,16 @@
return;
}
- // Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
- // (See AudioDeviceBroker#createBtDeviceInfo())
- int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
- int maxIndex = mStreamStates[streamType].getMaxIndex();
+ // Forcefully set LE audio volume as a workaround, since in some cases
+ // (like the outgoing call) the value of 'device' is not DEVICE_OUT_BLE_*
+ // even when BLE is connected.
+ if (!AudioSystem.isLeAudioDeviceType(device)) {
+ device = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+ }
+
+ final int streamType = getBluetoothContextualVolumeStream(mode);
+ final int index = mStreamStates[streamType].getIndex(device);
+ final int maxIndex = mStreamStates[streamType].getMaxIndex();
if (DEBUG_VOL) {
Log.d(TAG, "setLeAudioVolumeOnModeUpdate postSetLeAudioVolumeIndex index="
@@ -5421,9 +5427,7 @@
// change of mode may require volume to be re-applied on some devices
updateAbsVolumeMultiModeDevices(previousMode, mode);
- // Forcefully set LE audio volume as a workaround, since the value of 'device'
- // is not DEVICE_OUT_BLE_* even when BLE is connected.
- setLeAudioVolumeOnModeUpdate(mode, streamType, device);
+ setLeAudioVolumeOnModeUpdate(mode, device);
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index 1a5f31c..da43618 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -52,16 +52,13 @@
private boolean mDestroyed = false;
private boolean mDestroyRequested = false;
private boolean mDisableRequested = false;
- private volatile NextConsumer mNextConsumer = null;
+ private NextConsumer mNextConsumer = null;
private volatile float mLastAmbientLux = -1;
private final SensorEventListener mLightSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
- mLastAmbientLux = event.values[0];
- if (mNextConsumer != null) {
- completeNextConsumer(mLastAmbientLux);
- }
+ onNext(event.values[0]);
}
@Override
@@ -133,11 +130,29 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disable();
+ disableLightSensorLoggingLocked();
mDestroyed = true;
}
}
+ private synchronized void onNext(float value) {
+ mLastAmbientLux = value;
+
+ final NextConsumer consumer = mNextConsumer;
+ mNextConsumer = null;
+ if (consumer != null) {
+ Slog.v(TAG, "Finishing next consumer");
+
+ if (mDestroyRequested) {
+ destroy();
+ } else if (mDisableRequested) {
+ disable();
+ }
+
+ consumer.consume(value);
+ }
+ }
+
/** The most recent lux reading. */
public float getMostRecentLux() {
return mLastAmbientLux;
@@ -160,7 +175,7 @@
@Nullable Handler handler) {
final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
final float current = mLastAmbientLux;
- if (current > 0) {
+ if (current > -1f) {
nextConsumer.consume(current);
} else if (mDestroyed) {
nextConsumer.consume(-1f);
@@ -172,23 +187,6 @@
}
}
- private synchronized void completeNextConsumer(float value) {
- Slog.v(TAG, "Finishing next consumer");
-
- final NextConsumer consumer = mNextConsumer;
- mNextConsumer = null;
-
- if (mDestroyRequested) {
- destroy();
- } else if (mDisableRequested) {
- disable();
- }
-
- if (consumer != null) {
- consumer.consume(value);
- }
- }
-
private void enableLightSensorLoggingLocked() {
if (!mEnabled) {
mEnabled = true;
@@ -219,9 +217,13 @@
}
}
- private void onTimeout() {
+ private synchronized void onTimeout() {
Slog.e(TAG, "Max time exceeded for ALS logger - disabling: "
+ mLightSensorListener.hashCode());
+
+ // if consumers are waiting but there was no sensor change, complete them with the latest
+ // value before disabling
+ onNext(mLastAmbientLux);
disable();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index e4e9d01..27c4593 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -113,6 +113,16 @@
@Override
protected boolean registerWithService(GnssMeasurementRequest request,
Collection<GnssListenerRegistration> registrations) {
+ // The HAL doc does not specify if consecutive start() calls will be allowed.
+ // Some vendors may ignore the 2nd start() call if stop() is not called.
+ // Thus, here we always call stop() before calling start() to avoid being ignored.
+ if (mGnssNative.stopMeasurementCollection()) {
+ if (D) {
+ Log.d(TAG, "stopping gnss measurements");
+ }
+ } else {
+ Log.e(TAG, "error stopping gnss measurements");
+ }
if (mGnssNative.startMeasurementCollection(request.isFullTracking(),
request.isCorrelationVectorOutputsEnabled(),
request.getIntervalMillis())) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a55b118..37f980d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1978,34 +1978,39 @@
return (haystack & needle) != 0;
}
- public boolean isInLockDownMode() {
- return mIsInLockDownMode;
+ // Return whether the user is in lockdown mode.
+ // If the flag is not set, we assume the user is not in lockdown.
+ public boolean isInLockDownMode(int userId) {
+ return mUserInLockDownMode.get(userId, false);
}
@Override
public synchronized void onStrongAuthRequiredChanged(int userId) {
boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId),
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- mUserInLockDownMode.put(userId, userInLockDownModeNext);
- boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1;
- if (mIsInLockDownMode == isInLockDownModeNext) {
+ // Nothing happens if the lockdown mode of userId keeps the same.
+ if (userInLockDownModeNext == isInLockDownMode(userId)) {
return;
}
- if (isInLockDownModeNext) {
- cancelNotificationsWhenEnterLockDownMode();
+ // When the lockdown mode is changed, we perform the following steps.
+ // If the userInLockDownModeNext is true, all the function calls to
+ // notifyPostedLocked and notifyRemovedLocked will not be executed.
+ // The cancelNotificationsWhenEnterLockDownMode calls notifyRemovedLocked
+ // and postNotificationsWhenExitLockDownMode calls notifyPostedLocked.
+ // So we shall call cancelNotificationsWhenEnterLockDownMode before
+ // we set mUserInLockDownMode as true.
+ // On the other hand, if the userInLockDownModeNext is false, we shall call
+ // postNotificationsWhenExitLockDownMode after we put false into mUserInLockDownMode
+ if (userInLockDownModeNext) {
+ cancelNotificationsWhenEnterLockDownMode(userId);
}
- // When the mIsInLockDownMode is true, both notifyPostedLocked and
- // notifyRemovedLocked will be dismissed. So we shall call
- // cancelNotificationsWhenEnterLockDownMode before we set mIsInLockDownMode
- // as true and call postNotificationsWhenExitLockDownMode after we set
- // mIsInLockDownMode as false.
- mIsInLockDownMode = isInLockDownModeNext;
+ mUserInLockDownMode.put(userId, userInLockDownModeNext);
- if (!isInLockDownModeNext) {
- postNotificationsWhenExitLockDownMode();
+ if (!userInLockDownModeNext) {
+ postNotificationsWhenExitLockDownMode(userId);
}
}
}
@@ -9679,11 +9684,14 @@
}
}
- private void cancelNotificationsWhenEnterLockDownMode() {
+ private void cancelNotificationsWhenEnterLockDownMode(int userId) {
synchronized (mNotificationLock) {
int numNotifications = mNotificationList.size();
for (int i = 0; i < numNotifications; i++) {
NotificationRecord rec = mNotificationList.get(i);
+ if (rec.getUser().getIdentifier() != userId) {
+ continue;
+ }
mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL,
rec.getStats());
}
@@ -9691,14 +9699,23 @@
}
}
- private void postNotificationsWhenExitLockDownMode() {
+ private void postNotificationsWhenExitLockDownMode(int userId) {
synchronized (mNotificationLock) {
int numNotifications = mNotificationList.size();
+ // Set the delay to spread out the burst of notifications.
+ long delay = 0;
for (int i = 0; i < numNotifications; i++) {
NotificationRecord rec = mNotificationList.get(i);
- mListeners.notifyPostedLocked(rec, rec);
+ if (rec.getUser().getIdentifier() != userId) {
+ continue;
+ }
+ mHandler.postDelayed(() -> {
+ synchronized (mNotificationLock) {
+ mListeners.notifyPostedLocked(rec, rec);
+ }
+ }, delay);
+ delay += 20;
}
-
}
}
@@ -9877,6 +9894,9 @@
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
+ if (isInLockDownMode(record.getUser().getIdentifier())) {
+ continue;
+ }
if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) {
continue;
}
@@ -9918,8 +9938,8 @@
rankings.toArray(new NotificationListenerService.Ranking[0]));
}
- boolean isInLockDownMode() {
- return mStrongAuthTracker.isInLockDownMode();
+ boolean isInLockDownMode(int userId) {
+ return mStrongAuthTracker.isInLockDownMode(userId);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -10989,7 +11009,7 @@
@GuardedBy("mNotificationLock")
void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
boolean notifyAllListeners) {
- if (isInLockDownMode()) {
+ if (isInLockDownMode(r.getUser().getIdentifier())) {
return;
}
@@ -11095,7 +11115,7 @@
@GuardedBy("mNotificationLock")
public void notifyRemovedLocked(NotificationRecord r, int reason,
NotificationStats notificationStats) {
- if (isInLockDownMode()) {
+ if (isInLockDownMode(r.getUser().getIdentifier())) {
return;
}
@@ -11144,10 +11164,6 @@
*/
@GuardedBy("mNotificationLock")
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
- if (isInLockDownMode()) {
- return;
- }
-
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;
// TODO (b/73052211): if the ranking update changed the notification type,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 32ef014..fa0d41c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4052,6 +4052,9 @@
case KeyEvent.KEYCODE_DEMO_APP_2:
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4: {
+ // TODO(b/254604589): Dispatch KeyEvent to System UI.
+ sendSystemKeyToStatusBarAsync(keyCode);
+
// Just drop if keys are not intercepted for direct key.
result &= ~ACTION_PASS_TO_USER;
break;
@@ -4125,6 +4128,10 @@
mCameraGestureTriggered = true;
if (mRequestedOrSleepingDefaultDisplay) {
mCameraGestureTriggeredDuringGoingToSleep = true;
+ // Wake device up early to prevent display doing redundant turning off/on stuff.
+ wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey,
+ PowerManager.WAKE_REASON_CAMERA_LAUNCH,
+ "android.policy:CAMERA_GESTURE_PREVENT_LOCK");
}
return true;
}
@@ -4656,11 +4663,6 @@
}
mDefaultDisplayRotation.updateOrientationListener();
reportScreenStateToVrManager(false);
- if (mCameraGestureTriggeredDuringGoingToSleep) {
- wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey,
- PowerManager.WAKE_REASON_CAMERA_LAUNCH,
- "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
- }
}
}
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index 2c6fbbc9..fd1a3ac 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -192,41 +192,44 @@
// delivered asynchronously but enqueued until the step processing is finished.
boolean hasPrepared = false;
boolean hasTriggered = false;
+ boolean hasFailed = false;
long maxDuration = 0;
- try {
- hasPrepared = conductor.vibratorManagerHooks.prepareSyncedVibration(
- effectMapping.getRequiredSyncCapabilities(),
- effectMapping.getVibratorIds());
+ hasPrepared = conductor.vibratorManagerHooks.prepareSyncedVibration(
+ effectMapping.getRequiredSyncCapabilities(),
+ effectMapping.getVibratorIds());
- for (AbstractVibratorStep step : steps) {
- long duration = startVibrating(step, nextSteps);
- if (duration < 0) {
- // One vibrator has failed, fail this entire sync attempt.
- return maxDuration = -1;
- }
- maxDuration = Math.max(maxDuration, duration);
+ for (AbstractVibratorStep step : steps) {
+ long duration = startVibrating(step, nextSteps);
+ if (duration < 0) {
+ // One vibrator has failed, fail this entire sync attempt.
+ hasFailed = true;
+ break;
}
+ maxDuration = Math.max(maxDuration, duration);
+ }
- // Check if sync was prepared and if any step was accepted by a vibrator,
- // otherwise there is nothing to trigger here.
- if (hasPrepared && maxDuration > 0) {
- hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(
- getVibration().id);
- }
- return maxDuration;
- } finally {
- if (hasPrepared && !hasTriggered) {
- // Trigger has failed or all steps were ignored by the vibrators.
- conductor.vibratorManagerHooks.cancelSyncedVibration();
- nextSteps.clear();
- } else if (maxDuration < 0) {
- // Some vibrator failed without being prepared so other vibrators might be
- // active. Cancel and remove every pending step from output list.
- for (int i = nextSteps.size() - 1; i >= 0; i--) {
- nextSteps.remove(i).cancelImmediately();
- }
+ // Check if sync was prepared and if any step was accepted by a vibrator,
+ // otherwise there is nothing to trigger here.
+ if (hasPrepared && !hasFailed && maxDuration > 0) {
+ hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(getVibration().id);
+ hasFailed &= hasTriggered;
+ }
+
+ if (hasFailed) {
+ // Something failed, possibly after other vibrators were activated.
+ // Cancel and remove every pending step from output list.
+ for (int i = nextSteps.size() - 1; i >= 0; i--) {
+ nextSteps.remove(i).cancelImmediately();
}
}
+
+ // Cancel the preparation if trigger failed or all
+ if (hasPrepared && !hasTriggered) {
+ // Trigger has failed or was skipped, so abort the synced vibration.
+ conductor.vibratorManagerHooks.cancelSyncedVibration();
+ }
+
+ return hasFailed ? -1 : maxDuration;
}
private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5c1a877..27370bf 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1460,6 +1460,12 @@
|| transit == TRANSIT_OLD_ACTIVITY_RELAUNCH;
}
+ static boolean isTaskFragmentTransitOld(@TransitionOldType int transit) {
+ return transit == TRANSIT_OLD_TASK_FRAGMENT_OPEN
+ || transit == TRANSIT_OLD_TASK_FRAGMENT_CLOSE
+ || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+ }
+
static boolean isChangeTransitOld(@TransitionOldType int transit) {
return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
|| transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index bf4b65d..3a8fbbb 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -173,6 +173,7 @@
mWindowContainer = windowContainer;
// TODO: remove the frame provider for non-WindowState container.
mFrameProvider = frameProvider;
+ mOverrideFrames.clear();
mOverrideFrameProviders = overrideFrameProviders;
if (windowContainer == null) {
setServerVisible(false);
@@ -234,6 +235,8 @@
updateSourceFrameForServerVisibility();
if (mOverrideFrameProviders != null) {
+ // Not necessary to clear the mOverrideFrames here. It will be cleared every time the
+ // override frame provider updates.
for (int i = mOverrideFrameProviders.size() - 1; i >= 0; i--) {
final int windowType = mOverrideFrameProviders.keyAt(i);
final Rect overrideFrame;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index a469c6b..c19353c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -17,14 +17,17 @@
package com.android.server.wm;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.function.Function;
/** Reads letterbox configs from resources and controls their overrides at runtime. */
final class LetterboxConfiguration {
@@ -156,34 +159,25 @@
// portrait device orientation.
private boolean mIsVerticalReachabilityEnabled;
-
- // Horizontal position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getHorizontalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxHorizontalReachabilityPosition
- private volatile int mLetterboxPositionForHorizontalReachability;
-
- // Vertical position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getVerticalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxVerticalReachabilityPosition
- private volatile int mLetterboxPositionForVerticalReachability;
-
// Whether education is allowed for letterboxed fullscreen apps.
private boolean mIsEducationEnabled;
// Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
+ // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+ @NonNull
+ private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
LetterboxConfiguration(Context systemUiContext) {
+ this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext)));
+ }
+
+ @VisibleForTesting
+ LetterboxConfiguration(Context systemUiContext,
+ LetterboxConfigurationPersister letterboxConfigurationPersister) {
mContext = systemUiContext;
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
R.dimen.config_fixedOrientationLetterboxAspectRatio);
@@ -206,14 +200,14 @@
readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
mDefaultPositionForVerticalReachability =
readLetterboxVerticalReachabilityPositionFromConfig(mContext);
- mLetterboxPositionForHorizontalReachability = mDefaultPositionForHorizontalReachability;
- mLetterboxPositionForVerticalReachability = mDefaultPositionForVerticalReachability;
mIsEducationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEducationEnabled);
setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ mLetterboxConfigurationPersister = letterboxConfigurationPersister;
+ mLetterboxConfigurationPersister.start();
}
/**
@@ -653,7 +647,9 @@
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getHorizontalMultiplierForReachability() {
- switch (mLetterboxPositionForHorizontalReachability) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ switch (letterboxPositionForHorizontalReachability) {
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
return 0.0f;
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
@@ -662,10 +658,11 @@
return 1.0f;
default:
throw new AssertionError(
- "Unexpected letterbox position type: "
- + mLetterboxPositionForHorizontalReachability);
+ "Unexpected letterbox position type: "
+ + letterboxPositionForHorizontalReachability);
}
}
+
/*
* Gets vertical position of a center of the letterboxed app window when reachability
* is enabled specified. 0 corresponds to the top side of the screen and 1 to the bottom side.
@@ -673,7 +670,9 @@
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getVerticalMultiplierForReachability() {
- switch (mLetterboxPositionForVerticalReachability) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ switch (letterboxPositionForVerticalReachability) {
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
return 0.0f;
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
@@ -683,7 +682,7 @@
default:
throw new AssertionError(
"Unexpected letterbox position type: "
- + mLetterboxPositionForVerticalReachability);
+ + letterboxPositionForVerticalReachability);
}
}
@@ -693,7 +692,7 @@
*/
@LetterboxHorizontalReachabilityPosition
int getLetterboxPositionForHorizontalReachability() {
- return mLetterboxPositionForHorizontalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
}
/*
@@ -702,7 +701,7 @@
*/
@LetterboxVerticalReachabilityPosition
int getLetterboxPositionForVerticalReachability() {
- return mLetterboxPositionForVerticalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
}
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
@@ -742,9 +741,8 @@
* right side.
*/
void movePositionForHorizontalReachabilityToNextRightStop() {
- mLetterboxPositionForHorizontalReachability = Math.min(
- mLetterboxPositionForHorizontalReachability + 1,
- LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT);
+ updatePositionForHorizontalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
}
/**
@@ -752,8 +750,7 @@
* side.
*/
void movePositionForHorizontalReachabilityToNextLeftStop() {
- mLetterboxPositionForHorizontalReachability =
- Math.max(mLetterboxPositionForHorizontalReachability - 1, 0);
+ updatePositionForHorizontalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -761,9 +758,8 @@
* side.
*/
void movePositionForVerticalReachabilityToNextBottomStop() {
- mLetterboxPositionForVerticalReachability = Math.min(
- mLetterboxPositionForVerticalReachability + 1,
- LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM);
+ updatePositionForVerticalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
}
/**
@@ -771,8 +767,7 @@
* side.
*/
void movePositionForVerticalReachabilityToNextTopStop() {
- mLetterboxPositionForVerticalReachability =
- Math.max(mLetterboxPositionForVerticalReachability - 1, 0);
+ updatePositionForVerticalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -822,4 +817,26 @@
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
+ /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
+ private void updatePositionForHorizontalReachability(
+ Function<Integer, Integer> newHorizonalPositionFun) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int nextHorizontalPosition = newHorizonalPositionFun.apply(
+ letterboxPositionForHorizontalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ nextHorizontalPosition);
+ }
+
+ /** Calculates a new letterboxPositionForVerticalReachability value and updates the store */
+ private void updatePositionForVerticalReachability(
+ Function<Integer, Integer> newVerticalPositionFun) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int nextVerticalPosition = newVerticalPositionFun.apply(
+ letterboxPositionForVerticalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ nextVerticalPosition);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
new file mode 100644
index 0000000..70639b1
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition;
+import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition;
+import com.android.server.wm.nano.WindowManagerProtos;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Persists the values of letterboxPositionForHorizontalReachability and
+ * letterboxPositionForVerticalReachability for {@link LetterboxConfiguration}.
+ */
+class LetterboxConfigurationPersister {
+
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
+
+ @VisibleForTesting
+ static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+
+ private final Context mContext;
+ private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
+ private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
+
+ // Horizontal position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getHorizontalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxHorizontalReachabilityPosition
+ private volatile int mLetterboxPositionForHorizontalReachability;
+
+ // Vertical position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getVerticalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxVerticalReachabilityPosition
+ private volatile int mLetterboxPositionForVerticalReachability;
+
+ @NonNull
+ private final AtomicFile mConfigurationFile;
+
+ @Nullable
+ private final Consumer<String> mCompletionCallback;
+
+ @NonNull
+ private final PersisterQueue mPersisterQueue;
+
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier) {
+ this(systemUiContext, defaultHorizontalReachabilitySupplier,
+ defaultVerticalReachabilitySupplier,
+ Environment.getDataSystemDirectory(), new PersisterQueue(),
+ /* completionCallback */ null);
+ }
+
+ @VisibleForTesting
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier, File configFolder,
+ PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
+ mContext = systemUiContext.createDeviceProtectedStorageContext();
+ mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
+ mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
+ mCompletionCallback = completionCallback;
+ final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+ mConfigurationFile = new AtomicFile(prefFiles);
+ mPersisterQueue = persisterQueue;
+ readCurrentConfiguration();
+ }
+
+ /**
+ * Startes the persistence queue
+ */
+ void start() {
+ mPersisterQueue.startPersisting();
+ }
+
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForHorizontalReachability(
+ int letterboxPositionForHorizontalReachability) {
+ if (mLetterboxPositionForHorizontalReachability
+ != letterboxPositionForHorizontalReachability) {
+ mLetterboxPositionForHorizontalReachability =
+ letterboxPositionForHorizontalReachability;
+ updateConfiguration();
+ }
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForVerticalReachability(
+ int letterboxPositionForVerticalReachability) {
+ if (mLetterboxPositionForVerticalReachability != letterboxPositionForVerticalReachability) {
+ mLetterboxPositionForVerticalReachability = letterboxPositionForVerticalReachability;
+ updateConfiguration();
+ }
+ }
+
+ @VisibleForTesting
+ void useDefaultValue() {
+ mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get();
+ mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get();
+ }
+
+ private void readCurrentConfiguration() {
+ FileInputStream fis = null;
+ try {
+ fis = mConfigurationFile.openRead();
+ byte[] protoData = readInputStream(fis);
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ WindowManagerProtos.LetterboxProto.parseFrom(protoData);
+ mLetterboxPositionForHorizontalReachability =
+ letterboxData.letterboxPositionForHorizontalReachability;
+ mLetterboxPositionForVerticalReachability =
+ letterboxData.letterboxPositionForVerticalReachability;
+ } catch (IOException ioe) {
+ Slog.e(TAG,
+ "Error reading from LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ useDefaultValue();
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ useDefaultValue();
+ Slog.e(TAG, "Error reading from LetterboxConfigurationPersister ", e);
+ }
+ }
+ }
+ }
+
+ private void updateConfiguration() {
+ mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile,
+ mLetterboxPositionForHorizontalReachability,
+ mLetterboxPositionForVerticalReachability,
+ mCompletionCallback), /* flush */ true);
+ }
+
+ private static byte[] readInputStream(InputStream in) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ byte[] buffer = new byte[1024];
+ int size = in.read(buffer);
+ while (size > 0) {
+ outputStream.write(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ return outputStream.toByteArray();
+ } finally {
+ outputStream.close();
+ }
+ }
+
+ private static class UpdateValuesCommand implements
+ PersisterQueue.WriteQueueItem<UpdateValuesCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToUpdate;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+
+ private final int mHorizontalReachability;
+ private final int mVerticalReachability;
+
+ UpdateValuesCommand(@NonNull AtomicFile fileToUpdate,
+ int horizontalReachability, int verticalReachability,
+ @Nullable Consumer<String> onComplete) {
+ mFileToUpdate = fileToUpdate;
+ mHorizontalReachability = horizontalReachability;
+ mVerticalReachability = verticalReachability;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ new WindowManagerProtos.LetterboxProto();
+ letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability;
+ letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability;
+ final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData);
+
+ FileOutputStream fos = null;
+ try {
+ fos = mFileToUpdate.startWrite();
+ fos.write(bytes);
+ mFileToUpdate.finishWrite(fos);
+ } catch (IOException ioe) {
+ mFileToUpdate.failWrite(fos);
+ Slog.e(TAG,
+ "Error writing to LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ } finally {
+ if (mOnComplete != null) {
+ mOnComplete.accept("UpdateValuesCommand");
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index ac1a2b1..95371a5 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -320,11 +320,11 @@
} finally {
mService.closeSurfaceTransaction("RemoteAnimationController#finished");
}
+ // Reset input for all activities when the remote animation is finished.
+ final Consumer<ActivityRecord> updateActivities =
+ activity -> activity.setDropInputForAnimation(false);
+ mDisplayContent.forAllActivities(updateActivities);
}
- // Reset input for all activities when the remote animation is finished.
- final Consumer<ActivityRecord> updateActivities =
- activity -> activity.setDropInputForAnimation(false);
- mDisplayContent.forAllActivities(updateActivities);
setRunningRemoteAnimation(false);
ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 867833a..509b1e6 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -184,19 +184,30 @@
}
void dispose() {
- while (!mOrganizedTaskFragments.isEmpty()) {
- final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
- // Cleanup before remove to prevent it from sending any additional event, such as
- // #onTaskFragmentVanished, to the removed organizer.
+ for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) {
+ // Cleanup the TaskFragmentOrganizer from all TaskFragments it organized before
+ // removing the windows to prevent it from adding any additional TaskFragment
+ // pending event.
+ final TaskFragment taskFragment = mOrganizedTaskFragments.get(i);
taskFragment.onTaskFragmentOrganizerRemoved();
- taskFragment.removeImmediately();
- mOrganizedTaskFragments.remove(taskFragment);
}
+
+ // Defer to avoid unnecessary layout when there are multiple TaskFragments removal.
+ mAtmService.deferWindowLayout();
+ try {
+ while (!mOrganizedTaskFragments.isEmpty()) {
+ final TaskFragment taskFragment = mOrganizedTaskFragments.remove(0);
+ taskFragment.removeImmediately();
+ }
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
+
for (int i = mDeferredTransitions.size() - 1; i >= 0; i--) {
// Cleanup any running transaction to unblock the current transition.
onTransactionFinished(mDeferredTransitions.keyAt(i));
}
- mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
+ mOrganizer.asBinder().unlinkToDeath(this, 0 /* flags */);
}
@NonNull
@@ -426,7 +437,6 @@
@Override
public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
- validateAndGetState(organizer);
final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
@@ -607,6 +617,13 @@
int opType, @NonNull Throwable exception) {
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
+ final PendingTaskFragmentEvent vanishedEvent = taskFragment != null
+ ? getPendingTaskFragmentEvent(taskFragment, PendingTaskFragmentEvent.EVENT_VANISHED)
+ : null;
+ if (vanishedEvent != null) {
+ // No need to notify if the TaskFragment has been removed.
+ return;
+ }
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
@@ -690,11 +707,17 @@
}
private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
- final TaskFragmentOrganizerState state = validateAndGetState(organizer);
+ final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(
+ organizer.asBinder());
+ if (state == null) {
+ Slog.w(TAG, "The organizer has already been removed.");
+ return;
+ }
+ // Remove any pending event of this organizer first because state.dispose() may trigger
+ // event dispatch as result of surface placement.
+ mPendingTaskFragmentEvents.remove(organizer.asBinder());
// remove all of the children of the organized TaskFragment
state.dispose();
- // Remove any pending event of this organizer.
- mPendingTaskFragmentEvents.remove(organizer.asBinder());
mTaskFragmentOrganizerState.remove(organizer.asBinder());
}
@@ -878,23 +901,6 @@
return null;
}
- private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
- if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR
- // Always send parent info changed to update task visibility
- || event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
- return true;
- }
-
- final TaskFragmentOrganizerState state =
- mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
- final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
- final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo();
- // Send an info changed callback if this event is for the last activities to finish in a
- // TaskFragment so that the {@link TaskFragmentOrganizer} can delete this TaskFragment.
- return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
- && lastInfo != null && lastInfo.hasRunningActivity() && info.isEmpty();
- }
-
void dispatchPendingEvents() {
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|| mPendingTaskFragmentEvents.isEmpty()) {
@@ -908,37 +914,19 @@
}
}
- void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
+ private void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
@NonNull List<PendingTaskFragmentEvent> pendingEvents) {
if (pendingEvents.isEmpty()) {
return;
}
-
- final ArrayList<Task> visibleTasks = new ArrayList<>();
- final ArrayList<Task> invisibleTasks = new ArrayList<>();
- final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>();
- for (int i = 0, n = pendingEvents.size(); i < n; i++) {
- final PendingTaskFragmentEvent event = pendingEvents.get(i);
- final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
- // TODO(b/251132298): move visibility check to the client side.
- if (task != null && (task.lastActiveTime <= event.mDeferTime
- || !(isTaskVisible(task, visibleTasks, invisibleTasks)
- || shouldSendEventWhenTaskInvisible(event)))) {
- // Defer sending events to the TaskFragment until the host task is active again.
- event.mDeferTime = task.lastActiveTime;
- continue;
- }
- candidateEvents.add(event);
- }
- final int numEvents = candidateEvents.size();
- if (numEvents == 0) {
+ if (shouldDeferPendingEvents(state, pendingEvents)) {
return;
}
-
mTmpTaskSet.clear();
+ final int numEvents = pendingEvents.size();
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
for (int i = 0; i < numEvents; i++) {
- final PendingTaskFragmentEvent event = candidateEvents.get(i);
+ final PendingTaskFragmentEvent event = pendingEvents.get(i);
if (event.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED
|| event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
final Task task = event.mTaskFragment.getTask();
@@ -954,7 +942,47 @@
}
mTmpTaskSet.clear();
state.dispatchTransaction(transaction);
- pendingEvents.removeAll(candidateEvents);
+ pendingEvents.clear();
+ }
+
+ /**
+ * Whether or not to defer sending the events to the organizer to avoid waking the app process
+ * when it is in background. We want to either send all events or none to avoid inconsistency.
+ */
+ private boolean shouldDeferPendingEvents(@NonNull TaskFragmentOrganizerState state,
+ @NonNull List<PendingTaskFragmentEvent> pendingEvents) {
+ final ArrayList<Task> visibleTasks = new ArrayList<>();
+ final ArrayList<Task> invisibleTasks = new ArrayList<>();
+ for (int i = 0, n = pendingEvents.size(); i < n; i++) {
+ final PendingTaskFragmentEvent event = pendingEvents.get(i);
+ if (event.mEventType != PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED
+ && event.mEventType != PendingTaskFragmentEvent.EVENT_INFO_CHANGED
+ && event.mEventType != PendingTaskFragmentEvent.EVENT_APPEARED) {
+ // Send events for any other types.
+ return false;
+ }
+
+ // Check if we should send the event given the Task visibility and events.
+ final Task task;
+ if (event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
+ task = event.mTask;
+ } else {
+ task = event.mTaskFragment.getTask();
+ }
+ if (task.lastActiveTime > event.mDeferTime
+ && isTaskVisible(task, visibleTasks, invisibleTasks)) {
+ // Send events when the app has at least one visible Task.
+ return false;
+ } else if (shouldSendEventWhenTaskInvisible(task, state, event)) {
+ // Sent events even if the Task is invisible.
+ return false;
+ }
+
+ // Defer sending events to the organizer until the host task is active (visible) again.
+ event.mDeferTime = task.lastActiveTime;
+ }
+ // Defer for invisible Task.
+ return true;
}
private static boolean isTaskVisible(@NonNull Task task,
@@ -975,6 +1003,28 @@
}
}
+ private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task,
+ @NonNull TaskFragmentOrganizerState state,
+ @NonNull PendingTaskFragmentEvent event) {
+ final TaskFragmentParentInfo lastParentInfo = state.mLastSentTaskFragmentParentInfos
+ .get(task.mTaskId);
+ if (lastParentInfo == null || lastParentInfo.isVisible()) {
+ // When the Task was visible, or when there was no Task info changed sent (in which case
+ // the organizer will consider it as visible by default), always send the event to
+ // update the Task visibility.
+ return true;
+ }
+ if (event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
+ // Send info changed if the TaskFragment is becoming empty/non-empty so the
+ // organizer can choose whether or not to remove the TaskFragment.
+ final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos
+ .get(event.mTaskFragment);
+ final boolean isEmpty = event.mTaskFragment.getNonFinishingActivityCount() == 0;
+ return lastInfo == null || lastInfo.isEmpty() != isEmpty;
+ }
+ return false;
+ }
+
void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) {
final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 46253c1..32f6197 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1602,7 +1602,11 @@
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
change.setFlags(info.getChangeFlags(target));
+
final Task task = target.asTask();
+ final TaskFragment taskFragment = target.asTaskFragment();
+ final ActivityRecord activityRecord = target.asActivityRecord();
+
if (task != null) {
final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
task.fillTaskInfo(tinfo);
@@ -1636,12 +1640,7 @@
change.setEndRelOffset(bounds.left - parentBounds.left,
bounds.top - parentBounds.top);
int endRotation = target.getWindowConfiguration().getRotation();
- final ActivityRecord activityRecord = target.asActivityRecord();
if (activityRecord != null) {
- final Task arTask = activityRecord.getTask();
- final int backgroundColor = ColorUtils.setAlphaComponent(
- arTask.getTaskDescription().getBackgroundColor(), 255);
- change.setBackgroundColor(backgroundColor);
// TODO(b/227427984): Shell needs to aware letterbox.
// Always use parent bounds of activity because letterbox area (e.g. fixed aspect
// ratio or size compat mode) should be included in the animation.
@@ -1654,6 +1653,18 @@
} else {
change.setEndAbsBounds(bounds);
}
+
+ if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) {
+ // Set background color to Task theme color for activity and embedded TaskFragment
+ // in case we want to show background during the animation.
+ final Task parentTask = activityRecord != null
+ ? activityRecord.getTask()
+ : taskFragment.getTask();
+ final int backgroundColor = ColorUtils.setAlphaComponent(
+ parentTask.getTaskDescription().getBackgroundColor(), 255);
+ change.setBackgroundColor(backgroundColor);
+ }
+
change.setRotation(info.mRotation, endRotation);
if (info.mSnapshot != null) {
change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 07ae167..bece476 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -41,6 +41,7 @@
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.isActivityTransitOld;
+import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
import static com.android.server.wm.AppTransition.isTaskTransitOld;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -2985,10 +2986,17 @@
// {@link Activity#overridePendingTransition(int, int, int)}.
@ColorInt int backdropColor = 0;
if (controller.isFromActivityEmbedding()) {
- final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
- final Animation a = animAttr != 0
- ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
- showBackdrop = a != null && a.getShowBackdrop();
+ if (isChanging) {
+ // When there are more than one changing containers, it may leave part of the
+ // screen empty. Show background color to cover that.
+ showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
+ } else {
+ // Check whether or not to show backdrop for open/close transition.
+ final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
+ final Animation a = animAttr != 0
+ ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+ showBackdrop = a != null && a.getShowBackdrop();
+ }
backdropColor = appTransition.getNextAppTransitionBackgroundColor();
}
final Rect localBounds = new Rect(mTmpRect);
@@ -3091,9 +3099,16 @@
}
}
+ // Check if the animation requests to show background color for Activity and embedded
+ // TaskFragment.
final ActivityRecord activityRecord = asActivityRecord();
- if (activityRecord != null && isActivityTransitOld(transit)
- && adapter.getShowBackground()) {
+ final TaskFragment taskFragment = asTaskFragment();
+ if (adapter.getShowBackground()
+ // Check if it is Activity transition.
+ && ((activityRecord != null && isActivityTransitOld(transit))
+ // Check if it is embedded TaskFragment transition.
+ || (taskFragment != null && taskFragment.isEmbedded()
+ && isTaskFragmentTransitOld(transit)))) {
final @ColorInt int backgroundColorForTransition;
if (adapter.getBackgroundColor() != 0) {
// If available use the background color provided through getBackgroundColor
@@ -3103,9 +3118,11 @@
// Otherwise default to the window's background color if provided through
// the theme as the background color for the animation - the top most window
// with a valid background color and showBackground set takes precedence.
- final Task arTask = activityRecord.getTask();
+ final Task parentTask = activityRecord != null
+ ? activityRecord.getTask()
+ : taskFragment.getTask();
backgroundColorForTransition = ColorUtils.setAlphaComponent(
- arTask.getTaskDescription().getBackgroundColor(), 255);
+ parentTask.getTaskDescription().getBackgroundColor(), 255);
}
animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);
}
diff --git a/services/tests/mockingservicestests/OWNERS b/services/tests/mockingservicestests/OWNERS
index 2bb1649..4dda51f 100644
--- a/services/tests/mockingservicestests/OWNERS
+++ b/services/tests/mockingservicestests/OWNERS
@@ -1,5 +1,8 @@
include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
+
+# Game Platform
per-file FakeGameClassifier.java = file:/GAME_MANAGER_OWNERS
per-file FakeGameServiceProviderInstance = file:/GAME_MANAGER_OWNERS
per-file FakeServiceConnector.java = file:/GAME_MANAGER_OWNERS
per-file Game* = file:/GAME_MANAGER_OWNERS
+per-file res/xml/game_manager* = file:/GAME_MANAGER_OWNERS
diff --git a/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in.xml
new file mode 100644
index 0000000..77fe786
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsPerformanceGameMode="true"
+ android:supportsBatteryGameMode="true"
+ android:allowGameAngleDriver="false"
+ android:allowGameDownscaling="false"
+ android:allowGameFpsOverride="false"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in.xml
similarity index 100%
rename from services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml
rename to services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in.xml
diff --git a/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_all_opt_in.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_all_opt_in.xml
new file mode 100644
index 0000000..96d2878
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_all_opt_in.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsPerformanceGameMode="true"
+ android:supportsBatteryGameMode="true"
+ android:allowGameAngleDriver="true"
+ android:allowGameDownscaling="true"
+ android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml
similarity index 100%
rename from services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml
rename to services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml
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 d675b0a..cfb8014 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -19,6 +19,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -67,7 +68,9 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -87,6 +90,7 @@
private static final String PACKAGE_NAME_INVALID = "com.android.app";
private static final int USER_ID_1 = 1001;
private static final int USER_ID_2 = 1002;
+ private static final int DEFAULT_PACKAGE_UID = 12345;
private MockitoSession mMockingSession;
private String mPackageName;
@@ -194,6 +198,8 @@
.thenReturn(packages);
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
+ when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
+ DEFAULT_PACKAGE_UID);
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
}
@@ -369,38 +375,41 @@
.thenReturn(applicationInfo);
}
- private void mockInterventionsEnabledFromXml() throws Exception {
- final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
- mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
- Bundle metaDataBundle = new Bundle();
- final int resId = 123;
- metaDataBundle.putInt(
- GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
- applicationInfo.metaData = metaDataBundle;
- when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenReturn(applicationInfo);
- seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
- "res/xml/gama_manager_service_metadata_config_enabled.xml");
+ private void mockInterventionsEnabledNoOptInFromXml() throws Exception {
+ seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
+ "res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml");
}
- private void mockInterventionsDisabledFromXml() throws Exception {
- final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
- mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
- Bundle metaDataBundle = new Bundle();
- final int resId = 123;
- metaDataBundle.putInt(
- GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
- applicationInfo.metaData = metaDataBundle;
- when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenReturn(applicationInfo);
- seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
- "res/xml/gama_manager_service_metadata_config_disabled.xml");
+ private void mockInterventionsEnabledAllOptInFromXml() throws Exception {
+ seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
+ "res/xml/game_manager_service_metadata_config_interventions_enabled_all_opt_in"
+ + ".xml");
+ }
+
+ private void mockInterventionsDisabledNoOptInFromXml() throws Exception {
+ seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
+ "res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in"
+ + ".xml");
+ }
+
+ private void mockInterventionsDisabledAllOptInFromXml() throws Exception {
+ seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
+ "res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in"
+ + ".xml");
}
private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId,
String fileName)
throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ metaDataBundle.putInt(
+ GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
AssetManager assetManager =
InstrumentationRegistry.getInstrumentation().getContext().getAssets();
XmlResourceParser xmlResourceParser =
@@ -450,13 +459,13 @@
startUser(gameManagerService, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
mockModifyGameModeGranted();
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
// We need to make sure the mode is supported before setting it.
mockDeviceConfigAll();
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
@@ -534,8 +543,8 @@
startUser(gameManagerService, USER_ID_1);
startUser(gameManagerService, USER_ID_2);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
- gameManagerService.updateConfigsForUser(USER_ID_2, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_2, true, mPackageName);
// Set User 1 to Standard
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
@@ -563,7 +572,7 @@
if (gameManagerService == null) {
gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
}
ArraySet<Integer> reportedModes = new ArraySet<>();
int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
@@ -582,7 +591,7 @@
if (gameManagerService == null) {
gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
}
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName);
@@ -591,7 +600,7 @@
private void checkAngleEnabled(GameManagerService gameManagerService, int gameMode,
boolean angleEnabled) {
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
// Validate GamePackageConfiguration returns the correct value.
GameManagerService.GamePackageConfiguration config =
@@ -604,7 +613,7 @@
private void checkLoadingBoost(GameManagerService gameManagerService, int gameMode,
int loadingBoost) {
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
// Validate GamePackageConfiguration returns the correct value.
GameManagerService.GamePackageConfiguration config =
@@ -621,13 +630,19 @@
if (gameManagerService == null) {
gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
}
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName);
assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
}
+ private boolean checkOptedIn(GameManagerService gameManagerService, int gameMode) {
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ return config.willGamePerformOptimizations(gameMode);
+ }
+
/**
* Phenotype device config exists, but is only propagating the default value.
*/
@@ -743,7 +758,7 @@
* Override device configs for both battery and performance modes exists and are valid.
*/
@Test
- public void testSetDeviceOverrideConfigAll() {
+ public void testSetDeviceConfigOverrideAll() {
mockDeviceConfigAll();
mockModifyGameModeGranted();
@@ -763,6 +778,75 @@
checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60);
}
+ @Test
+ public void testSetBatteryModeConfigOverride_thenUpdateAllDeviceConfig() throws Exception {
+ mockModifyGameModeGranted();
+ String configStringBefore =
+ "mode=2,downscaleFactor=1.0,fps=90:mode=3,downscaleFactor=0.1,fps=30";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configStringBefore);
+ mockInterventionsEnabledNoOptInFromXml();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+
+ checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "1.0");
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
+ checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.1");
+ checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);
+
+ gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
+ "0.2");
+
+ checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
+ checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.2");
+
+ String configStringAfter =
+ "mode=2,downscaleFactor=0.9,fps=60:mode=3,downscaleFactor=0.3,fps=50";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configStringAfter);
+ gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
+
+ // performance mode was not overridden thus it should be updated
+ checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.9");
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 60);
+
+ // battery mode was overridden thus it should be the same as the override
+ checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.2");
+ checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
+ }
+
+ @Test
+ public void testSetBatteryModeConfigOverride_thenOptInBatteryMode() throws Exception {
+ mockModifyGameModeGranted();
+ String configStringBefore =
+ "mode=2,downscaleFactor=1.0,fps=90:mode=3,downscaleFactor=0.1,fps=30";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configStringBefore);
+ mockInterventionsDisabledNoOptInFromXml();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+
+ assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+ assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
+
+ gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
+ "0.2");
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
+ // override will enable the interventions
+ checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.2");
+ checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
+
+ mockInterventionsDisabledAllOptInFromXml();
+ gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
+
+ assertTrue(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+ // opt-in is still false for battery mode as override exists
+ assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+ }
+
/**
* Override device config for performance mode exists and is valid.
*/
@@ -1037,7 +1121,7 @@
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
- mockInterventionsEnabledFromXml();
+ mockInterventionsEnabledNoOptInFromXml();
checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
}
@@ -1045,7 +1129,7 @@
public void testGameModeConfigAllowFpsTrue() throws Exception {
mockDeviceConfigAll();
mockModifyGameModeGranted();
- mockInterventionsEnabledFromXml();
+ mockInterventionsEnabledNoOptInFromXml();
GameManagerService gameManagerService = new GameManagerService(mMockContext,
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
@@ -1060,7 +1144,7 @@
public void testGameModeConfigAllowFpsFalse() throws Exception {
mockDeviceConfigAll();
mockModifyGameModeGranted();
- mockInterventionsDisabledFromXml();
+ mockInterventionsDisabledNoOptInFromXml();
GameManagerService gameManagerService = new GameManagerService(mMockContext,
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
@@ -1091,7 +1175,7 @@
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName);
assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
@@ -1109,7 +1193,7 @@
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
}
@@ -1126,7 +1210,7 @@
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
}
@@ -1143,7 +1227,7 @@
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED, USER_ID_1);
- gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+ gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
}
@@ -1404,4 +1488,80 @@
assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
}
+
+ @Test
+ public void testResetInterventions_onDeviceConfigReset() throws Exception {
+ mockModifyGameModeGranted();
+ String configStringBefore =
+ "mode=2,downscaleFactor=1.0,fps=90";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configStringBefore);
+ mockInterventionsEnabledNoOptInFromXml();
+ GameManagerService gameManagerService = Mockito.spy(new GameManagerService(mMockContext,
+ mTestLooper.getLooper()));
+ startUser(gameManagerService, USER_ID_1);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ Mockito.verify(gameManagerService).setOverrideFrameRate(
+ ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
+ ArgumentMatchers.eq(90.0f));
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
+
+ String configStringAfter = "";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configStringAfter);
+ gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
+ Mockito.verify(gameManagerService).setOverrideFrameRate(
+ ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
+ ArgumentMatchers.eq(0.0f));
+ }
+
+ @Test
+ public void testResetInterventions_onInterventionsDisabled() throws Exception {
+ mockModifyGameModeGranted();
+ String configStringBefore =
+ "mode=2,downscaleFactor=1.0,fps=90";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configStringBefore);
+ mockInterventionsEnabledNoOptInFromXml();
+ GameManagerService gameManagerService = Mockito.spy(new GameManagerService(mMockContext,
+ mTestLooper.getLooper()));
+ startUser(gameManagerService, USER_ID_1);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ Mockito.verify(gameManagerService).setOverrideFrameRate(
+ ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
+ ArgumentMatchers.eq(90.0f));
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
+
+ mockInterventionsDisabledNoOptInFromXml();
+ gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
+ Mockito.verify(gameManagerService).setOverrideFrameRate(
+ ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
+ ArgumentMatchers.eq(0.0f));
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
+ }
+
+ @Test
+ public void testResetInterventions_onGameModeOptedIn() throws Exception {
+ mockModifyGameModeGranted();
+ String configStringBefore =
+ "mode=2,downscaleFactor=1.0,fps=90";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configStringBefore);
+ mockInterventionsEnabledNoOptInFromXml();
+ GameManagerService gameManagerService = Mockito.spy(new GameManagerService(mMockContext,
+ mTestLooper.getLooper()));
+ startUser(gameManagerService, USER_ID_1);
+
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ Mockito.verify(gameManagerService).setOverrideFrameRate(
+ ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
+ ArgumentMatchers.eq(90.0f));
+ checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
+
+ mockInterventionsEnabledAllOptInFromXml();
+ gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
+ Mockito.verify(gameManagerService).setOverrideFrameRate(
+ ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
+ ArgumentMatchers.eq(0.0f));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 68c9ce4..0cff4f1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -178,6 +178,23 @@
}
@Test
+ public void testWatchDogCompletesAwait() {
+ mProbe.enable();
+
+ AtomicInteger lux = new AtomicInteger(-9);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ moveTimeBy(TIMEOUT_MS);
+
+ assertThat(lux.get()).isEqualTo(-1);
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+ }
+
+ @Test
public void testNextLuxWhenAlreadyEnabledAndNotAvailable() {
testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 039e159..7c2cd94 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -117,6 +117,10 @@
public class VibratorManagerServiceTest {
private static final int TEST_TIMEOUT_MILLIS = 1_000;
+ // Time to allow for a cancellation to complete (notably including system ramp down), but not so
+ // long that tests easily get really slow or flaky. If a vibration is close to this, it should
+ // be cancelled in the body of the individual test.
+ private static final int CLEANUP_TIMEOUT_MILLIS = 100;
private static final int UID = Process.ROOT_UID;
private static final int VIRTUAL_DISPLAY_ID = 1;
private static final String PACKAGE_NAME = "package";
@@ -162,6 +166,7 @@
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
+ private VibratorManagerService mService;
private Context mContextSpy;
private TestLooper mTestLooper;
private FakeVibrator mVibrator;
@@ -226,8 +231,27 @@
@After
public void tearDown() throws Exception {
+ if (mService != null) {
+ // Wait until all vibrators have stopped vibrating, with a bit of flexibility for tests
+ // that just do a click or have cancelled at the end (waiting for ramp-down).
+ //
+ // Note: if a test is flaky here, check whether a VibrationEffect duration is close to
+ // CLEANUP_TIMEOUT_MILLIS - in which case it's probably best to just cancel that effect
+ // explicitly at the end of the test case (rather than letting it run and race flakily).
+ assertTrue(waitUntil(s -> {
+ for (int vibratorId : mService.getVibratorIds()) {
+ if (s.isVibrating(vibratorId)) {
+ return false;
+ }
+ }
+ return true;
+ }, mService, CLEANUP_TIMEOUT_MILLIS));
+ }
+
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(PowerManagerInternal.class);
+ // Ignore potential exceptions about the looper having never dispatched any messages.
+ mTestLooper.stopAutoDispatchAndIgnoreExceptions();
}
private VibratorManagerService createSystemReadyService() {
@@ -237,7 +261,7 @@
}
private VibratorManagerService createService() {
- return new VibratorManagerService(
+ mService = new VibratorManagerService(
mContextSpy,
new VibratorManagerService.Injector() {
@Override
@@ -274,6 +298,7 @@
(VibratorManagerService.ExternalVibratorService) serviceInstance;
}
});
+ return mService;
}
@Test
@@ -474,6 +499,7 @@
verify(listeners[0]).onVibrating(eq(true));
verify(listeners[1]).onVibrating(eq(true));
verify(listeners[2], never()).onVibrating(eq(true));
+ cancelVibrate(service);
}
@Test
@@ -821,7 +847,7 @@
assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
.anyMatch(PrebakedSegment.class::isInstance));
// Clean up repeating effect.
- service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+ cancelVibrate(service);
}
@Test
@@ -876,6 +902,8 @@
// The second vibration should have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+
+ cancelVibrate(service); // Clean up repeating effect.
}
@Test
@@ -904,7 +932,7 @@
assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
.anyMatch(PrebakedSegment.class::isInstance));
// Clean up long effect.
- service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+ cancelVibrate(service);
}
@Test
@@ -912,6 +940,7 @@
throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createSystemReadyService();
VibrationEffect effect = VibrationEffect.createWaveform(
@@ -922,14 +951,16 @@
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, effect, RINGTONE_ATTRS);
-
- // VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ RINGTONE_ATTRS);
// The second vibration should have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+ // One segment played is the prebaked CLICK from the second vibration.
+ assertEquals(1,
+ mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .filter(PrebakedSegment.class::isInstance)
+ .count());
}
@Test
@@ -1220,10 +1251,11 @@
service.updateServiceState();
// Vibration is not stopped nearly after updating service.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+ cancelVibrate(service);
}
@Test
- public void vibrate_withVitualDisplayChange_ignoreVibrationFromVirtualDisplay()
+ public void vibrate_withVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
throws Exception {
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -1249,10 +1281,11 @@
// Haptic feedback played normally when the virtual display is removed.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ cancelVibrate(service); // Clean up long-ish effect.
}
@Test
- public void vibrate_withAppsOnVitualDisplayChange_ignoreVibrationFromVirtualDisplay()
+ public void vibrate_withAppsOnVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
throws Exception {
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -1277,7 +1310,7 @@
HAPTIC_FEEDBACK_ATTRS);
// Haptic feedback played normally when the same app no long runs on a virtual display.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
+ cancelVibrate(service); // Clean up long-ish effect.
}
@Test
@@ -1916,6 +1949,10 @@
when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
}
+ private void cancelVibrate(VibratorManagerService service) {
+ service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+ }
+
private IVibratorStateListener mockVibratorStateListener() {
IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
IBinder binderMock = mock(IBinder.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 101a5cb..bf66dee 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -488,63 +488,112 @@
@Test
public void testNotifyPostedLockedInLockdownMode() {
- NotificationRecord r = mock(NotificationRecord.class);
- NotificationRecord old = mock(NotificationRecord.class);
+ NotificationRecord r0 = mock(NotificationRecord.class);
+ NotificationRecord old0 = mock(NotificationRecord.class);
+ UserHandle uh0 = mock(UserHandle.class);
- // before the lockdown mode
- when(mNm.isInLockDownMode()).thenReturn(false);
- mListeners.notifyPostedLocked(r, old, true);
- mListeners.notifyPostedLocked(r, old, false);
- verify(r, atLeast(2)).getSbn();
+ NotificationRecord r1 = mock(NotificationRecord.class);
+ NotificationRecord old1 = mock(NotificationRecord.class);
+ UserHandle uh1 = mock(UserHandle.class);
- // in the lockdown mode
- reset(r);
- reset(old);
- when(mNm.isInLockDownMode()).thenReturn(true);
- mListeners.notifyPostedLocked(r, old, true);
- mListeners.notifyPostedLocked(r, old, false);
- verify(r, never()).getSbn();
- }
+ // Neither user0 and user1 is in the lockdown mode
+ when(r0.getUser()).thenReturn(uh0);
+ when(uh0.getIdentifier()).thenReturn(0);
+ when(mNm.isInLockDownMode(0)).thenReturn(false);
- @Test
- public void testnotifyRankingUpdateLockedInLockdownMode() {
- List chn = mock(List.class);
+ when(r1.getUser()).thenReturn(uh1);
+ when(uh1.getIdentifier()).thenReturn(1);
+ when(mNm.isInLockDownMode(1)).thenReturn(false);
- // before the lockdown mode
- when(mNm.isInLockDownMode()).thenReturn(false);
- mListeners.notifyRankingUpdateLocked(chn);
- verify(chn, atLeast(1)).size();
+ mListeners.notifyPostedLocked(r0, old0, true);
+ mListeners.notifyPostedLocked(r0, old0, false);
+ verify(r0, atLeast(2)).getSbn();
- // in the lockdown mode
- reset(chn);
- when(mNm.isInLockDownMode()).thenReturn(true);
- mListeners.notifyRankingUpdateLocked(chn);
- verify(chn, never()).size();
+ mListeners.notifyPostedLocked(r1, old1, true);
+ mListeners.notifyPostedLocked(r1, old1, false);
+ verify(r1, atLeast(2)).getSbn();
+
+ // Reset
+ reset(r0);
+ reset(old0);
+ reset(r1);
+ reset(old1);
+
+ // Only user 0 is in the lockdown mode
+ when(r0.getUser()).thenReturn(uh0);
+ when(uh0.getIdentifier()).thenReturn(0);
+ when(mNm.isInLockDownMode(0)).thenReturn(true);
+
+ when(r1.getUser()).thenReturn(uh1);
+ when(uh1.getIdentifier()).thenReturn(1);
+ when(mNm.isInLockDownMode(1)).thenReturn(false);
+
+ mListeners.notifyPostedLocked(r0, old0, true);
+ mListeners.notifyPostedLocked(r0, old0, false);
+ verify(r0, never()).getSbn();
+
+ mListeners.notifyPostedLocked(r1, old1, true);
+ mListeners.notifyPostedLocked(r1, old1, false);
+ verify(r1, atLeast(2)).getSbn();
}
@Test
public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException {
- NotificationRecord r = mock(NotificationRecord.class);
- NotificationStats rs = mock(NotificationStats.class);
+ NotificationRecord r0 = mock(NotificationRecord.class);
+ NotificationStats rs0 = mock(NotificationStats.class);
+ UserHandle uh0 = mock(UserHandle.class);
+
+ NotificationRecord r1 = mock(NotificationRecord.class);
+ NotificationStats rs1 = mock(NotificationStats.class);
+ UserHandle uh1 = mock(UserHandle.class);
+
StatusBarNotification sbn = mock(StatusBarNotification.class);
FieldSetter.setField(mNm,
NotificationManagerService.class.getDeclaredField("mHandler"),
mock(NotificationManagerService.WorkerHandler.class));
- // before the lockdown mode
- when(mNm.isInLockDownMode()).thenReturn(false);
- when(r.getSbn()).thenReturn(sbn);
- mListeners.notifyRemovedLocked(r, 0, rs);
- mListeners.notifyRemovedLocked(r, 0, rs);
- verify(r, atLeast(2)).getSbn();
+ // Neither user0 and user1 is in the lockdown mode
+ when(r0.getUser()).thenReturn(uh0);
+ when(uh0.getIdentifier()).thenReturn(0);
+ when(mNm.isInLockDownMode(0)).thenReturn(false);
+ when(r0.getSbn()).thenReturn(sbn);
- // in the lockdown mode
- reset(r);
- reset(rs);
- when(mNm.isInLockDownMode()).thenReturn(true);
- when(r.getSbn()).thenReturn(sbn);
- mListeners.notifyRemovedLocked(r, 0, rs);
- mListeners.notifyRemovedLocked(r, 0, rs);
- verify(r, never()).getSbn();
+ when(r1.getUser()).thenReturn(uh1);
+ when(uh1.getIdentifier()).thenReturn(1);
+ when(mNm.isInLockDownMode(1)).thenReturn(false);
+ when(r1.getSbn()).thenReturn(sbn);
+
+ mListeners.notifyRemovedLocked(r0, 0, rs0);
+ mListeners.notifyRemovedLocked(r0, 0, rs0);
+ verify(r0, atLeast(2)).getSbn();
+
+ mListeners.notifyRemovedLocked(r1, 0, rs1);
+ mListeners.notifyRemovedLocked(r1, 0, rs1);
+ verify(r1, atLeast(2)).getSbn();
+
+ // Reset
+ reset(r0);
+ reset(rs0);
+ reset(r1);
+ reset(rs1);
+
+ // Only user 0 is in the lockdown mode
+ when(r0.getUser()).thenReturn(uh0);
+ when(uh0.getIdentifier()).thenReturn(0);
+ when(mNm.isInLockDownMode(0)).thenReturn(true);
+ when(r0.getSbn()).thenReturn(sbn);
+
+ when(r1.getUser()).thenReturn(uh1);
+ when(uh1.getIdentifier()).thenReturn(1);
+ when(mNm.isInLockDownMode(1)).thenReturn(false);
+ when(r1.getSbn()).thenReturn(sbn);
+
+ mListeners.notifyRemovedLocked(r0, 0, rs0);
+ mListeners.notifyRemovedLocked(r0, 0, rs0);
+ verify(r0, never()).getSbn();
+
+ mListeners.notifyRemovedLocked(r1, 0, rs1);
+ mListeners.notifyRemovedLocked(r1, 0, rs1);
+ verify(r1, atLeast(2)).getSbn();
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a545c83..beaa6e0 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -174,6 +174,7 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenPolicy;
@@ -9781,10 +9782,10 @@
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
- assertTrue(mStrongAuthTracker.isInLockDownMode());
- mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
+ assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId()));
+ mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId());
mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
- assertFalse(mStrongAuthTracker.isInLockDownMode());
+ assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId()));
}
@Test
@@ -9800,8 +9801,8 @@
// when entering the lockdown mode, cancel the 2 notifications.
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
- assertTrue(mStrongAuthTracker.isInLockDownMode());
+ mStrongAuthTracker.onStrongAuthRequiredChanged(0);
+ assertTrue(mStrongAuthTracker.isInLockDownMode(0));
// the notifyRemovedLocked function is called twice due to REASON_CANCEL_ALL.
ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
@@ -9810,10 +9811,46 @@
// exit lockdown mode.
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
- mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
+ mStrongAuthTracker.onStrongAuthRequiredChanged(0);
+ assertFalse(mStrongAuthTracker.isInLockDownMode(0));
// the notifyPostedLocked function is called twice.
- verify(mListeners, times(2)).notifyPostedLocked(any(), any());
+ verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong());
+ //verify(mListeners, times(2)).notifyPostedLocked(any(), any());
+ }
+
+ @Test
+ public void testMakeRankingUpdateLockedInLockDownMode() {
+ // post 2 notifications from a same package
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 1), mTestNotificationChannel);
+ mService.addNotification(pkgB);
+
+ mService.setIsVisibleToListenerReturnValue(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null);
+ assertEquals(2, nru.getRankingMap().getOrderedKeys().length);
+
+ // when only user 0 entering the lockdown mode, its notification will be suppressed.
+ mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ mStrongAuthTracker.onStrongAuthRequiredChanged(0);
+ assertTrue(mStrongAuthTracker.isInLockDownMode(0));
+ assertFalse(mStrongAuthTracker.isInLockDownMode(1));
+
+ nru = mService.makeRankingUpdateLocked(null);
+ assertEquals(1, nru.getRankingMap().getOrderedKeys().length);
+
+ // User 0 exits lockdown mode. Its notification will be resumed.
+ mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
+ mStrongAuthTracker.onStrongAuthRequiredChanged(0);
+ assertFalse(mStrongAuthTracker.isInLockDownMode(0));
+ assertFalse(mStrongAuthTracker.isInLockDownMode(1));
+
+ nru = mService.makeRankingUpdateLocked(null);
+ assertEquals(2, nru.getRankingMap().getOrderedKeys().length);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index b49e5cb..8cf74fb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -19,10 +19,12 @@
import android.companion.ICompanionDeviceManager;
import android.content.ComponentName;
import android.content.Context;
+import android.service.notification.StatusBarNotification;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceIdSequence;
+import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import java.util.HashSet;
import java.util.Set;
@@ -37,6 +39,9 @@
@Nullable
NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
+ @Nullable
+ Boolean mIsVisibleToListenerReturnValue = null;
+
TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
InstanceIdSequence notificationInstanceIdSequence) {
super(context, logger, notificationInstanceIdSequence);
@@ -119,6 +124,19 @@
mShowReviewPermissionsNotification = setting;
}
+ protected void setIsVisibleToListenerReturnValue(boolean value) {
+ mIsVisibleToListenerReturnValue = value;
+ }
+
+ @Override
+ boolean isVisibleToListener(StatusBarNotification sbn, int notificationType,
+ ManagedServiceInfo listener) {
+ if (mIsVisibleToListenerReturnValue != null) {
+ return mIsVisibleToListenerReturnValue;
+ }
+ return super.isVisibleToListener(sbn, notificationType, listener);
+ }
+
public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
private int mGetStrongAuthForUserReturnValue = 0;
StrongAuthTrackerFake(Context context) {
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
index dd0c162..f494f72 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
@@ -51,6 +51,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -87,6 +88,7 @@
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
}
+ @Ignore("b/253871109")
@Test
public void testAddPinCreatesPinned() throws RemoteException {
grantSlicePermission();
@@ -97,6 +99,7 @@
verify(mService, times(1)).createPinnedSlice(eq(maybeAddUserId(TEST_URI, 0)), anyString());
}
+ @Ignore("b/253871109")
@Test
public void testRemovePinDestroysPinned() throws RemoteException {
grantSlicePermission();
@@ -109,6 +112,7 @@
verify(mCreatedSliceState, never()).destroy();
}
+ @Ignore("b/253871109")
@Test
public void testCheckAutoGrantPermissions() throws RemoteException {
String[] testPerms = new String[] {
@@ -129,12 +133,14 @@
verify(mContextSpy).checkPermission(eq("perm2"), eq(Process.myPid()), eq(Process.myUid()));
}
+ @Ignore("b/253871109")
@Test(expected = IllegalStateException.class)
public void testNoPinThrow() throws Exception {
grantSlicePermission();
mService.getPinnedSpecs(TEST_URI, "pkg");
}
+ @Ignore("b/253871109")
@Test
public void testGetPinnedSpecs() throws Exception {
grantSlicePermission();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
new file mode 100644
index 0000000..1246d1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationPersisterTest {
+
+ private static final long TIMEOUT = 2000L; // 2 secs
+
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+ private Context mContext;
+ private PersisterQueue mPersisterQueue;
+ private QueueState mQueueState;
+ private PersisterQueue.Listener mQueueListener;
+ private File mConfigFolder;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = getInstrumentation().getTargetContext();
+ mConfigFolder = mContext.getFilesDir();
+ mPersisterQueue = new PersisterQueue();
+ mQueueState = new QueueState();
+ mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability),
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability),
+ mConfigFolder, mPersisterQueue, mQueueState);
+ mQueueListener = queueEmpty -> mQueueState.onItemAdded();
+ mPersisterQueue.addListener(mQueueListener);
+ mLetterboxConfigurationPersister.start();
+ }
+
+ public void tearDown() throws InterruptedException {
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ mPersisterQueue.removeListener(mQueueListener);
+ stopPersisterSafe(mPersisterQueue);
+ }
+
+ @Test
+ public void test_whenStoreIsCreated_valuesAreDefaults() {
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreWritten() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
+ final PersisterQueue firstPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), firstPersisterQueue,
+ mQueueState);
+ firstPersister.start();
+ firstPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ firstPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(firstPersisterQueue);
+ stopPersisterSafe(firstPersisterQueue);
+ final PersisterQueue secondPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), secondPersisterQueue,
+ mQueueState);
+ secondPersister.start();
+ final int newPositionForHorizontalReachability =
+ secondPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ secondPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(secondPersister, secondPersisterQueue);
+ waitForCompletion(secondPersisterQueue);
+ stopPersisterSafe(secondPersisterQueue);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ private void stopPersisterSafe(PersisterQueue persisterQueue) {
+ try {
+ persisterQueue.stopPersisting();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void waitForCompletion(PersisterQueue persisterQueue) {
+ final long endTime = System.currentTimeMillis() + TIMEOUT;
+ // The queue could be empty but the last item still processing and not completed. For this
+ // reason the completion happens when there are not more items to process and the last one
+ // has completed.
+ while (System.currentTimeMillis() < endTime && (!isQueueEmpty(persisterQueue)
+ || !hasLastItemCompleted())) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) { /* Nope */}
+ }
+ }
+
+ private boolean isQueueEmpty(PersisterQueue persisterQueue) {
+ return persisterQueue.findLastItem(
+ writeQueueItem -> true, PersisterQueue.WriteQueueItem.class) != null;
+ }
+
+ private boolean hasLastItemCompleted() {
+ return mQueueState.isEmpty();
+ }
+
+ private void deleteConfiguration(LetterboxConfigurationPersister persister,
+ PersisterQueue persisterQueue) {
+ final AtomicFile fileToDelete = new AtomicFile(
+ new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+ persisterQueue.addItem(
+ new DeleteFileCommand(fileToDelete, mQueueState.andThen(
+ s -> persister.useDefaultValue())), true);
+ }
+
+ private static class DeleteFileCommand implements
+ PersisterQueue.WriteQueueItem<DeleteFileCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToDelete;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+ DeleteFileCommand(@NonNull AtomicFile fileToDelete, Consumer<String> onComplete) {
+ mFileToDelete = fileToDelete;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ mFileToDelete.delete();
+ if (mOnComplete != null) {
+ mOnComplete.accept("DeleteFileCommand");
+ }
+ }
+ }
+
+ // Contains the current length of the persister queue
+ private static class QueueState implements Consumer<String> {
+
+ // The current number of commands in the queue
+ @VisibleForTesting
+ private final AtomicInteger mCounter = new AtomicInteger(0);
+
+ @Override
+ public void accept(String s) {
+ mCounter.decrementAndGet();
+ }
+
+ void onItemAdded() {
+ mCounter.incrementAndGet();
+ }
+
+ boolean isEmpty() {
+ return mCounter.get() == 0;
+ }
+
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
new file mode 100644
index 0000000..c927f9e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationTest {
+
+ private LetterboxConfiguration mLetterboxConfiguration;
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = getInstrumentation().getTargetContext();
+ mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class);
+ mLetterboxConfiguration = new LetterboxConfiguration(context,
+ mLetterboxConfigurationPersister);
+ }
+
+ @Test
+ public void test_whenReadingValues_storeIsInvoked() {
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability();
+ }
+
+ @Test
+ public void test_whenSettingValues_updateConfigurationIsInvoked() {
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ anyInt());
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ anyInt());
+ }
+
+ @Test
+ public void test_whenMovedHorizontally_updatePositionAccordingly() {
+ // Starting from center
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from left
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from right
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ }
+
+ @Test
+ public void test_whenMovedVertically_updatePositionAccordingly() {
+ // Starting from center
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from top
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from bottom
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ }
+
+ private void assertForHorizontalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForHorizontalReachability(
+ expected);
+ }
+
+ private void assertForVerticalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForVerticalReachability(
+ expected);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 0b23359..4202f46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -56,6 +56,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -91,6 +92,7 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.List;
@@ -762,6 +764,50 @@
}
@Test
+ public void testOrganizerRemovedWithPendingEvents() {
+ final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ final TaskFragment tf1 = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ assertTrue(tf0.isOrganizedTaskFragment());
+ assertTrue(tf1.isOrganizedTaskFragment());
+ assertTrue(tf0.isAttached());
+ assertTrue(tf0.isAttached());
+
+ // Mock the behavior that remove TaskFragment can trigger event dispatch.
+ final Answer<Void> removeImmediately = invocation -> {
+ invocation.callRealMethod();
+ mController.dispatchPendingEvents();
+ return null;
+ };
+ doAnswer(removeImmediately).when(tf0).removeImmediately();
+ doAnswer(removeImmediately).when(tf1).removeImmediately();
+
+ // Add pending events.
+ mController.onTaskFragmentAppeared(mIOrganizer, tf0);
+ mController.onTaskFragmentAppeared(mIOrganizer, tf1);
+
+ // Remove organizer.
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.dispatchPendingEvents();
+
+ // Nothing should happen after the organizer is removed.
+ verify(mOrganizer, never()).onTransactionReady(any());
+
+ // TaskFragments should be removed.
+ assertFalse(tf0.isOrganizedTaskFragment());
+ assertFalse(tf1.isOrganizedTaskFragment());
+ assertFalse(tf0.isAttached());
+ assertFalse(tf0.isAttached());
+ }
+
+ @Test
public void testTaskFragmentInPip_startActivityInTaskFragment() {
setupTaskFragmentInPip();
final ActivityRecord activity = mTaskFragment.getTopMostActivity();
@@ -874,29 +920,87 @@
@Test
public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
- // Task - TaskFragment - Activity.
final Task task = createTask(mDisplayContent);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setOrganizer(mOrganizer)
.setFragmentToken(mFragmentToken)
.build();
-
- // Mock the task to invisible
doReturn(false).when(task).shouldBeVisible(any());
- // Sending events
- taskFragment.mTaskFragmentAppearedSent = true;
+ // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTransactionReady(any());
+
+ // Verify that events were not sent when the Task is in background.
+ clearInvocations(mOrganizer);
+ final Rect bounds = new Rect(0, 0, 500, 1000);
+ task.setBoundsUnchecked(bounds);
+ mController.onTaskFragmentParentInfoChanged(mIOrganizer, task);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
-
- // Verifies that event was not sent
verify(mOrganizer, never()).onTransactionReady(any());
+
+ // Verify that the events were sent when the Task becomes visible.
+ doReturn(true).when(task).shouldBeVisible(any());
+ task.lastActiveTime++;
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTransactionReady(any());
+ }
+
+ @Test
+ public void testSendAllPendingTaskFragmentEventsWhenAnyTaskIsVisible() {
+ // Invisible Task.
+ final Task invisibleTask = createTask(mDisplayContent);
+ final TaskFragment invisibleTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(invisibleTask)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ doReturn(false).when(invisibleTask).shouldBeVisible(any());
+
+ // Visible Task.
+ final IBinder fragmentToken = new Binder();
+ final Task visibleTask = createTask(mDisplayContent);
+ final TaskFragment visibleTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(visibleTask)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(fragmentToken)
+ .build();
+ doReturn(true).when(invisibleTask).shouldBeVisible(any());
+
+ // Sending events
+ invisibleTaskFragment.mTaskFragmentAppearedSent = true;
+ visibleTaskFragment.mTaskFragmentAppearedSent = true;
+ mController.onTaskFragmentInfoChanged(mIOrganizer, invisibleTaskFragment);
+ mController.onTaskFragmentInfoChanged(mIOrganizer, visibleTaskFragment);
+ mController.dispatchPendingEvents();
+
+ // Verify that both events are sent.
+ verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
+ final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
+ final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+
+ // There should be two Task info changed with two TaskFragment info changed.
+ assertEquals(4, changes.size());
+ // Invisible Task info changed
+ assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, changes.get(0).getType());
+ assertEquals(invisibleTask.mTaskId, changes.get(0).getTaskId());
+ // Invisible TaskFragment info changed
+ assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, changes.get(1).getType());
+ assertEquals(invisibleTaskFragment.getFragmentToken(),
+ changes.get(1).getTaskFragmentToken());
+ // Visible Task info changed
+ assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, changes.get(2).getType());
+ assertEquals(visibleTask.mTaskId, changes.get(2).getTaskId());
+ // Visible TaskFragment info changed
+ assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, changes.get(3).getType());
+ assertEquals(visibleTaskFragment.getFragmentToken(), changes.get(3).getTaskFragmentToken());
}
@Test
public void testCanSendPendingTaskFragmentEventsAfterActivityResumed() {
- // Task - TaskFragment - Activity.
final Task task = createTask(mDisplayContent);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
@@ -905,24 +1009,26 @@
.createActivityCount(1)
.build();
final ActivityRecord activity = taskFragment.getTopMostActivity();
-
- // Mock the task to invisible
doReturn(false).when(task).shouldBeVisible(any());
taskFragment.setResumedActivity(null, "test");
- // Sending events
- taskFragment.mTaskFragmentAppearedSent = true;
+ // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTransactionReady(any());
+
+ // Verify the info changed event is not sent because the Task is invisible
+ clearInvocations(mOrganizer);
+ final Rect bounds = new Rect(0, 0, 500, 1000);
+ task.setBoundsUnchecked(bounds);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
-
- // Verifies that event was not sent
verify(mOrganizer, never()).onTransactionReady(any());
- // Mock the task becomes visible, and activity resumed
+ // Mock the task becomes visible, and activity resumed. Verify the info changed event is
+ // sent.
doReturn(true).when(task).shouldBeVisible(any());
taskFragment.setResumedActivity(activity, "test");
-
- // Verifies that event is sent.
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
}
@@ -977,25 +1083,24 @@
final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
// Add another activity in the Task so that it always contains a non-finishing activity.
createActivityRecord(task);
- assertTrue(task.shouldBeVisible(null));
+ doReturn(false).when(task).shouldBeVisible(any());
- // Dispatch pending info changed event from creating the activity
- taskFragment.mTaskFragmentAppearedSent = true;
- mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+ // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
- // Verify the info changed callback is not called when the task is invisible
+ // Verify the info changed event is not sent because the Task is invisible
clearInvocations(mOrganizer);
- doReturn(false).when(task).shouldBeVisible(any());
+ final Rect bounds = new Rect(0, 0, 500, 1000);
+ task.setBoundsUnchecked(bounds);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTransactionReady(any());
- // Finish the embedded activity, and verify the info changed callback is called because the
+ // Finish the embedded activity, and verify the info changed event is sent because the
// TaskFragment is becoming empty.
embeddedActivity.finishing = true;
- mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 9fd0850..66e46a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -59,7 +59,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -79,6 +81,8 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.internal.graphics.ColorUtils;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -1384,6 +1388,50 @@
}
@Test
+ public void testChangeSetBackgroundColor() {
+ final Transition transition = createTestTransition(TRANSIT_CHANGE);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ // Test background color for Activity and embedded TaskFragment.
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+ final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ final ActivityManager.TaskDescription taskDescription =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW)
+ .build();
+ task.setTaskDescription(taskDescription);
+
+ // Start states:
+ embeddedActivity.mVisibleRequested = true;
+ nonEmbeddedActivity.mVisibleRequested = false;
+ changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
+ changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
+ // End states:
+ embeddedActivity.mVisibleRequested = false;
+ nonEmbeddedActivity.mVisibleRequested = true;
+
+ participants.add(embeddedTf);
+ participants.add(nonEmbeddedActivity);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType,
+ 0 /* flags */, targets, changes, mMockT);
+
+ // Background color should be set on both Activity and embedded TaskFragment.
+ final int expectedBackgroundColor = ColorUtils.setAlphaComponent(
+ taskDescription.getBackgroundColor(), 255);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(expectedBackgroundColor, info.getChanges().get(0).getBackgroundColor());
+ assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
+ }
+
+ @Test
public void testTransitionVisibleChange() {
registerTestTransitionPlayer();
final ActivityRecord app = createActivityRecord(mDisplayContent);