Merge changes I2536ae30,Ieaef83a2 into udc-qpr-dev
* changes:
Do not run face auth if keyguard is already authenticated with face.
Do not make device is asleep a gating condition for face auth
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 06635ee..5d076d4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -683,7 +683,7 @@
public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
/**
- * Works in the same way as {@link #BIND_EXTERNAL_SERVICE}, but it's defined as a (@code long)
+ * Works in the same way as {@link #BIND_EXTERNAL_SERVICE}, but it's defined as a {@code long}
* value that is compatible to {@link BindServiceFlags}.
*/
public static final long BIND_EXTERNAL_SERVICE_LONG = 1L << 62;
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 6195443..f594377 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -980,23 +980,6 @@
}
/**
- * @hide
- */
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
- if (mService == null) {
- Slog.w(TAG, "setUdfpsOverlay: no fingerprint service");
- return;
- }
-
- try {
- mService.setUdfpsOverlay(controller);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Forwards BiometricStateListener to FingerprintService
* @param listener new BiometricStateListener being added
* @hide
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ff2f313..9975852 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -27,7 +27,6 @@
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -203,10 +202,6 @@
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void setSidefpsController(in ISidefpsController controller);
- // Sets the controller for managing the UDFPS overlay.
- @EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void setUdfpsOverlay(in IUdfpsOverlay controller);
-
// Registers BiometricStateListener.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
deleted file mode 100644
index c99fccc..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
+++ /dev/null
@@ -1,29 +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 android.hardware.fingerprint;
-
-/**
- * Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
- * @hide
- */
-oneway interface IUdfpsOverlay {
- // Shows the overlay.
- void show(long requestId, int sensorId, int reason);
-
- // Hides the overlay.
- void hide(int sensorId);
-}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d37c37a..3da9e96 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8105,6 +8105,16 @@
private final Paint mHighlightPaint;
private final Path mHighlightPath;
+ /**
+ * Whether it is in the progress of updating transformation method. It's needed because
+ * {@link TextView#setTransformationMethod(TransformationMethod)} will eventually call
+ * {@link TextView#setText(CharSequence)}.
+ * Because it normally should exit insert mode when {@link TextView#setText(CharSequence)}
+ * is called externally, we need this boolean to distinguish whether setText is triggered
+ * by setTransformation or not.
+ */
+ private boolean mUpdatingTransformationMethod;
+
InsertModeController(@NonNull TextView textView) {
mTextView = Objects.requireNonNull(textView);
mIsInsertModeActive = false;
@@ -8137,7 +8147,7 @@
final boolean isSingleLine = mTextView.isSingleLine();
mInsertModeTransformationMethod = new InsertModeTransformationMethod(offset,
isSingleLine, oldTransformationMethod);
- mTextView.setTransformationMethodInternal(mInsertModeTransformationMethod);
+ setTransformationMethod(mInsertModeTransformationMethod, true);
Selection.setSelection((Spannable) mTextView.getText(), offset);
mIsInsertModeActive = true;
@@ -8145,6 +8155,10 @@
}
void exitInsertMode() {
+ exitInsertMode(true);
+ }
+
+ void exitInsertMode(boolean updateText) {
if (!mIsInsertModeActive) return;
if (mInsertModeTransformationMethod == null
|| mInsertModeTransformationMethod != mTextView.getTransformationMethod()) {
@@ -8157,7 +8171,7 @@
final int selectionEnd = mTextView.getSelectionEnd();
final TransformationMethod oldTransformationMethod =
mInsertModeTransformationMethod.getOldTransformationMethod();
- mTextView.setTransformationMethodInternal(oldTransformationMethod);
+ setTransformationMethod(oldTransformationMethod, updateText);
Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
mIsInsertModeActive = false;
}
@@ -8178,6 +8192,32 @@
}
/**
+ * Update the TransformationMethod on the {@link TextView}.
+ * @param method the new method to be set on the {@link TextView}/
+ * @param updateText whether to update the text during setTransformationMethod call.
+ */
+ private void setTransformationMethod(TransformationMethod method, boolean updateText) {
+ mUpdatingTransformationMethod = true;
+ mTextView.setTransformationMethodInternal(method, updateText);
+ mUpdatingTransformationMethod = false;
+ }
+
+ /**
+ * Notify the InsertMode controller that the {@link TextView} is about to set its text.
+ */
+ void beforeSetText() {
+ // TextView#setText is called because our call to
+ // TextView#setTransformationMethodInternal in enterInsertMode() or exitInsertMode().
+ // Do nothing in this case.
+ if (mUpdatingTransformationMethod) {
+ return;
+ }
+ // TextView#setText is called externally. Exit InsertMode but don't update text again
+ // when calling setTransformationMethod.
+ exitInsertMode(/* updateText */ false);
+ }
+
+ /**
* Notify the {@link InsertModeController} before the TextView's
* {@link TransformationMethod} is updated. If it's not in the insert mode,
* the given method is directly returned. Otherwise, it will wrap the given transformation
@@ -8205,6 +8245,9 @@
return mInsertModeController.enterInsertMode(offset);
}
+ /**
+ * Exit insert mode if this editor is in insert mode.
+ */
void exitInsertMode() {
if (mInsertModeController == null) return;
mInsertModeController.exitInsertMode();
@@ -8217,7 +8260,7 @@
*/
void setTransformationMethod(TransformationMethod method) {
if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) {
- mTextView.setTransformationMethodInternal(method);
+ mTextView.setTransformationMethodInternal(method, /* updateText */ true);
return;
}
@@ -8226,11 +8269,19 @@
final int selectionStart = mTextView.getSelectionStart();
final int selectionEnd = mTextView.getSelectionEnd();
method = mInsertModeController.updateTransformationMethod(method);
- mTextView.setTransformationMethodInternal(method);
+ mTextView.setTransformationMethodInternal(method, /* updateText */ true);
Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
}
/**
+ * Notify that the Editor that the associated {@link TextView} is about to set its text.
+ */
+ void beforeSetText() {
+ if (mInsertModeController == null) return;
+ mInsertModeController.beforeSetText();
+ }
+
+ /**
* Initializes the nodeInfo with smart actions.
*/
void onInitializeSmartActionsAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7e1e52d..438b974 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2795,11 +2795,22 @@
if (mEditor != null) {
mEditor.setTransformationMethod(method);
} else {
- setTransformationMethodInternal(method);
+ setTransformationMethodInternal(method, /* updateText */ true);
}
}
- void setTransformationMethodInternal(@Nullable TransformationMethod method) {
+ /**
+ * Set the transformation that is applied to the text that this TextView is displaying,
+ * optionally call the setText.
+ * @param method the new transformation method to be set.
+ * @param updateText whether the call {@link #setText} which will update the TextView to display
+ * the new content. This method is helpful when updating
+ * {@link TransformationMethod} inside {@link #setText}. It should only be
+ * false if text will be updated immediately after this call, otherwise the
+ * TextView will enter an inconsistent state.
+ */
+ void setTransformationMethodInternal(@Nullable TransformationMethod method,
+ boolean updateText) {
if (method == mTransformation) {
// Avoid the setText() below if the transformation is
// the same.
@@ -2821,7 +2832,9 @@
mAllowTransformationLengthChange = false;
}
- setText(mText);
+ if (updateText) {
+ setText(mText);
+ }
if (hasPasswordTransformationMethod()) {
notifyViewAccessibilityStateChangedIfNeeded(
@@ -7000,6 +7013,9 @@
@UnsupportedAppUsage
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
+ if (mEditor != null) {
+ mEditor.beforeSetText();
+ }
mTextSetFromXmlOrResourceId = false;
if (text == null) {
text = "";
@@ -13811,13 +13827,14 @@
}
/**
- * Helper method to set {@code rect} to the text content's non-clipped area in the view's
- * coordinates.
+ * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
+ * This method obtains the view's visible rectangle whereas the method
+ * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
*
* @return true if at least part of the text content is visible; false if the text content is
* completely clipped or translated out of the visible area.
*/
- private boolean getContentVisibleRect(Rect rect) {
+ private boolean getViewVisibleRect(Rect rect) {
if (!getLocalVisibleRect(rect)) {
return false;
}
@@ -13826,6 +13843,20 @@
// view's coordinates. So we need to offset it with the negative scrolled amount to convert
// it to view's coordinate.
rect.offset(-getScrollX(), -getScrollY());
+ return true;
+ }
+
+ /**
+ * Helper method to set {@code rect} to the text content's non-clipped area in the view's
+ * coordinates.
+ *
+ * @return true if at least part of the text content is visible; false if the text content is
+ * completely clipped or translated out of the visible area.
+ */
+ private boolean getContentVisibleRect(Rect rect) {
+ if (!getViewVisibleRect(rect)) {
+ return false;
+ }
// Clip the view's visible rect with the text layout's visible rect.
return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
@@ -13955,14 +13986,25 @@
builder.setMatrix(viewToScreenMatrix);
if (includeEditorBounds) {
- final RectF editorBounds = new RectF();
- editorBounds.set(0 /* left */, 0 /* top */,
- getWidth(), getHeight());
- final RectF handwritingBounds = new RectF(
- -getHandwritingBoundsOffsetLeft(),
- -getHandwritingBoundsOffsetTop(),
- getWidth() + getHandwritingBoundsOffsetRight(),
- getHeight() + getHandwritingBoundsOffsetBottom());
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ final Rect bounds = mTempRect;
+ final RectF editorBounds;
+ final RectF handwritingBounds;
+ if (getViewVisibleRect(bounds)) {
+ editorBounds = new RectF(bounds);
+ handwritingBounds = new RectF(editorBounds);
+ handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+ handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+ handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+ handwritingBounds.right += getHandwritingBoundsOffsetRight();
+ } else {
+ // The editor is not visible at all, return empty rectangles. We still need to
+ // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
+ editorBounds = new RectF();
+ handwritingBounds = new RectF();
+ }
EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
.setHandwritingBounds(handwritingBounds).build();
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 07ee9b5..d4dd1e7 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -21,6 +21,8 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -42,7 +44,7 @@
@RemoteViews.RemoteView
public class NotificationExpandButton extends FrameLayout {
- private View mPillView;
+ private Drawable mPillDrawable;
private TextView mNumberView;
private ImageView mIconView;
private boolean mExpanded;
@@ -73,7 +75,10 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPillView = findViewById(R.id.expand_button_pill);
+
+ final View pillView = findViewById(R.id.expand_button_pill);
+ final LayerDrawable layeredPill = (LayerDrawable) pillView.getBackground();
+ mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer);
mNumberView = findViewById(R.id.expand_button_number);
mIconView = findViewById(R.id.expand_button_icon);
}
@@ -156,7 +161,7 @@
private void updateColors() {
if (shouldShowNumber()) {
if (mHighlightPillColor != 0) {
- mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+ mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
}
mIconView.setColorFilter(mHighlightTextColor);
if (mHighlightTextColor != 0) {
@@ -164,7 +169,7 @@
}
} else {
if (mDefaultPillColor != 0) {
- mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+ mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
}
mIconView.setColorFilter(mDefaultTextColor);
if (mDefaultTextColor != 0) {
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..a794d53
--- /dev/null
+++ b/core/res/res/color-night/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
+ <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
+ <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..67b2c25
--- /dev/null
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
+ <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
+ <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/expand_button_pill_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml
index f95044a..a14d33c 100644
--- a/core/res/res/drawable/expand_button_pill_bg.xml
+++ b/core/res/res/drawable/expand_button_pill_bg.xml
@@ -13,7 +13,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <corners android:radius="@dimen/notification_expand_button_pill_height" />
- <solid android:color="@android:color/white" />
-</shape>
\ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/expand_button_pill_colorized_layer">
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="@dimen/notification_expand_button_pill_height" />
+ <solid android:color="@android:color/white" />
+ </shape>
+ </item>
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="@dimen/notification_expand_button_pill_height" />
+ <solid android:color="@color/notification_expand_button_state_tint" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index 8eae064..63fe471 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -34,6 +34,7 @@
android:background="@drawable/expand_button_pill_bg"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
+ android:duplicateParentState="true"
>
<TextView
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f27535c..3be0d7f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -46,6 +46,7 @@
<item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_connected_display</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
@@ -72,6 +73,7 @@
<string translatable="false" name="status_bar_sync_failing">sync_failing</string>
<string translatable="false" name="status_bar_sync_active">sync_active</string>
<string translatable="false" name="status_bar_cast">cast</string>
+ <string translatable="false" name="status_bar_connected_display">connected_display</string>
<string translatable="false" name="status_bar_hotspot">hotspot</string>
<string translatable="false" name="status_bar_location">location</string>
<string translatable="false" name="status_bar_bluetooth">bluetooth</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2e5da33..85e9792 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3050,6 +3050,7 @@
<java-symbol type="id" name="header_text_secondary" />
<java-symbol type="id" name="expand_button" />
<java-symbol type="id" name="expand_button_pill" />
+ <java-symbol type="id" name="expand_button_pill_colorized_layer" />
<java-symbol type="id" name="expand_button_number" />
<java-symbol type="id" name="expand_button_icon" />
<java-symbol type="id" name="alternate_expand_target" />
@@ -3099,6 +3100,7 @@
<java-symbol type="string" name="status_bar_sync_failing" />
<java-symbol type="string" name="status_bar_sync_active" />
<java-symbol type="string" name="status_bar_cast" />
+ <java-symbol type="string" name="status_bar_connected_display" />
<java-symbol type="string" name="status_bar_hotspot" />
<java-symbol type="string" name="status_bar_location" />
<java-symbol type="string" name="status_bar_bluetooth" />
diff --git a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
index 1a01987..62adc20 100644
--- a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
+++ b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
@@ -30,6 +30,7 @@
import android.view.Gravity;
import android.view.View;
import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorBoundsInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -54,8 +55,15 @@
private static final int[] sLocationOnScreen = new int[2];
private static Typeface sTypeface;
private static final float TEXT_SIZE = 1f;
- // The line height of the test font font is 1.2 * textSize.
+ // The line height of the test font is 1.2 * textSize.
private static final int LINE_HEIGHT = 12;
+ private static final int HW_BOUNDS_OFFSET_LEFT = 10;
+ private static final int HW_BOUNDS_OFFSET_TOP = 20;
+ private static final int HW_BOUNDS_OFFSET_RIGHT = 30;
+ private static final int HW_BOUNDS_OFFSET_BOTTOM = 40;
+
+
+ // Default text has 5 lines of text. The needed width is 50px and the needed height is 60px.
private static final CharSequence DEFAULT_TEXT = "X\nXX\nXXX\nXXXX\nXXXXX";
private static final ImmutableList<RectF> DEFAULT_LINE_BOUNDS = ImmutableList.of(
new RectF(0f, 0f, 10f, LINE_HEIGHT),
@@ -131,6 +139,55 @@
}
@Test
+ public void testEditorBoundsInfo_allVisible() {
+ // The needed width and height of the DEFAULT_TEXT are 50 px and 60 px respectfully.
+ int width = 100;
+ int height = 200;
+ setupEditText(DEFAULT_TEXT, width, height);
+ CursorAnchorInfo cursorAnchorInfo =
+ mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+ EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+ assertThat(editorBoundsInfo).isNotNull();
+ assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, width, height));
+ assertThat(editorBoundsInfo.getHandwritingBounds())
+ .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, -HW_BOUNDS_OFFSET_TOP,
+ width + HW_BOUNDS_OFFSET_RIGHT, height + HW_BOUNDS_OFFSET_BOTTOM));
+ }
+
+ @Test
+ public void testEditorBoundsInfo_scrolled() {
+ // The height of the editor will be 60 px.
+ int width = 100;
+ int visibleTop = 10;
+ int visibleBottom = 30;
+ setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+ CursorAnchorInfo cursorAnchorInfo =
+ mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+ EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+ assertThat(editorBoundsInfo).isNotNull();
+ assertThat(editorBoundsInfo.getEditorBounds())
+ .isEqualTo(new RectF(0, visibleTop, width, visibleBottom));
+ assertThat(editorBoundsInfo.getHandwritingBounds())
+ .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, visibleTop - HW_BOUNDS_OFFSET_TOP,
+ width + HW_BOUNDS_OFFSET_RIGHT, visibleBottom + HW_BOUNDS_OFFSET_BOTTOM));
+ }
+
+ @Test
+ public void testEditorBoundsInfo_invisible() {
+ // The height of the editor will be 60px. Scroll it to 70px will make it invisible.
+ int width = 100;
+ int visibleTop = 70;
+ int visibleBottom = 70;
+ setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+ CursorAnchorInfo cursorAnchorInfo =
+ mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+ EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+ assertThat(editorBoundsInfo).isNotNull();
+ assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+ assertThat(editorBoundsInfo.getHandwritingBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+ }
+
+ @Test
public void testVisibleLineBounds_allVisible() {
setupEditText(DEFAULT_TEXT, /* height= */ 100);
CursorAnchorInfo cursorAnchorInfo =
@@ -465,32 +522,26 @@
}
private void setupVerticalClippedEditText(int visibleTop, int visibleBottom) {
+ setupVerticalClippedEditText(1000, visibleTop, visibleBottom);
+ }
+
+ /**
+ * Helper method to create an EditText in a vertical ScrollView so that its visible bounds
+ * is Rect(0, visibleTop, width, visibleBottom) in the EditText's coordinates. Both ScrollView
+ * and EditText's width is set to the given width.
+ */
+ private void setupVerticalClippedEditText(int width, int visibleTop, int visibleBottom) {
ScrollView scrollView = new ScrollView(mActivity);
- mEditText = new EditText(mActivity);
- mEditText.setTypeface(sTypeface);
- mEditText.setText(DEFAULT_TEXT);
- mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
-
- mEditText.setPadding(0, 0, 0, 0);
- mEditText.setCompoundDrawables(null, null, null, null);
- mEditText.setCompoundDrawablePadding(0);
-
- mEditText.scrollTo(0, 0);
- mEditText.setLineSpacing(0f, 1f);
-
- // Place the text layout top to the view's top.
- mEditText.setGravity(Gravity.TOP);
- int width = 1000;
- int height = visibleBottom - visibleTop;
+ createEditText();
+ int scrollViewHeight = visibleBottom - visibleTop;
scrollView.addView(mEditText, new FrameLayout.LayoutParams(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(5 * LINE_HEIGHT, View.MeasureSpec.EXACTLY)));
scrollView.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
- scrollView.layout(0, 0, width, height);
-
+ View.MeasureSpec.makeMeasureSpec(scrollViewHeight, View.MeasureSpec.EXACTLY));
+ scrollView.layout(0, 0, width, scrollViewHeight);
scrollView.scrollTo(0, visibleTop);
}
@@ -499,6 +550,11 @@
measureEditText(height);
}
+ private void setupEditText(CharSequence text, int width, int height) {
+ createEditText(text);
+ measureEditText(width, height);
+ }
+
private void setupEditText(CharSequence text, int height, float lineSpacing,
float lineMultiplier) {
createEditText(text);
@@ -537,6 +593,8 @@
mEditText.setTypeface(sTypeface);
mEditText.setText(text);
mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
+ mEditText.setHandwritingBoundsOffsets(HW_BOUNDS_OFFSET_LEFT, HW_BOUNDS_OFFSET_TOP,
+ HW_BOUNDS_OFFSET_RIGHT, HW_BOUNDS_OFFSET_BOTTOM);
mEditText.setPadding(0, 0, 0, 0);
mEditText.setCompoundDrawables(null, null, null, null);
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2e3f604..14e8253 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -419,4 +419,8 @@
<dimen name="freeform_resize_handle">15dp</dimen>
<dimen name="freeform_resize_corner">44dp</dimen>
+
+ <!-- The height of the area at the top of the screen where a freeform task will transition to
+ fullscreen if dragged until the top bound of the task is within the area. -->
+ <dimen name="desktop_mode_transition_area_height">16dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 65a35b2..4fda4b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -100,6 +100,10 @@
}
}
+ private val transitionAreaHeight
+ get() = context.resources.getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+
init {
desktopMode = DesktopModeImpl()
if (DesktopModeStatus.isProto2Enabled()) {
@@ -700,13 +704,12 @@
y: Float
) {
if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- val statusBarHeight = getStatusBarHeight(taskInfo)
- if (y <= statusBarHeight && visualIndicator == null) {
+ if (y <= transitionAreaHeight && visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
displayController, context, taskSurface, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer)
visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
- } else if (y > statusBarHeight && visualIndicator != null) {
+ } else if (y > transitionAreaHeight && visualIndicator != null) {
releaseVisualIndicator()
}
}
@@ -726,8 +729,7 @@
y: Float,
windowDecor: DesktopModeWindowDecoration
) {
- val statusBarHeight = getStatusBarHeight(taskInfo)
- if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(taskInfo, position)
}
@@ -746,9 +748,9 @@
taskSurface: SurfaceControl,
y: Float
) {
- // If the motion event is above the status bar, return since we do not need to show the
- // visual indicator at this point.
- if (y < getStatusBarHeight(taskInfo)) {
+ // If the motion event is above the status bar and the visual indicator is not yet visible,
+ // return since we do not need to show the visual indicator at this point.
+ if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
return
}
if (visualIndicator == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 92c2a7c..cf16920 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -193,7 +193,7 @@
final DragPositioningCallback dragPositioningCallback =
new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
- null /* disallowedAreaForEndBounds */);
+ 0 /* disallowedAreaForEndBoundsHeight */);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 331835c..7245bc9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -845,7 +845,7 @@
windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
- windowDecoration, taskInfo);
+ windowDecoration);
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
@@ -858,24 +858,17 @@
incrementEventReceiverTasks(taskInfo.displayId);
}
private DragPositioningCallback createDragPositioningCallback(
- @NonNull DesktopModeWindowDecoration windowDecoration,
- @NonNull RunningTaskInfo taskInfo) {
- final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
- final Rect disallowedAreaForEndBounds;
- if (DesktopModeStatus.isProto2Enabled()) {
- disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
- getStatusBarHeight(taskInfo.displayId));
- } else {
- disallowedAreaForEndBounds = null;
- }
+ @NonNull DesktopModeWindowDecoration windowDecoration) {
+ final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.desktop_mode_transition_area_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
- mTransactionFactory);
+ mDisplayController, mDragStartListener, mTransactionFactory,
+ transitionAreaHeight);
} else {
return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
- mTransitions);
+ mDisplayController, mDragStartListener, mTransitions,
+ transitionAreaHeight);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 09e29bc..e32bd42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -83,8 +83,6 @@
// Make sure the new resizing destination in any direction falls within the stable bounds.
// If not, set the bounds back to the old location that was valid to avoid conflicts with
// some regions such as the gesture area.
- displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
- .getStableBounds(stableBounds);
if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
final int candidateLeft = repositionTaskBounds.left + (int) delta.x;
repositionTaskBounds.left = (candidateLeft > stableBounds.left)
@@ -136,7 +134,7 @@
repositionTaskBounds.top);
}
- static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
PointF repositionStartPoint, float x, float y) {
final float deltaX = x - repositionStartPoint.x;
final float deltaY = y - repositionStartPoint.y;
@@ -145,6 +143,23 @@
}
/**
+ * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
+ * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
+ * stable bounds.
+ */
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
+ PointF repositionStartPoint, float x, float y) {
+ updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
+ x, y);
+
+ // If task is outside of stable bounds (in the status bar area), shift the task down.
+ if (stableBounds.top > repositionTaskBounds.top) {
+ final int yShift = stableBounds.top - repositionTaskBounds.top;
+ repositionTaskBounds.offset(0, yShift);
+ }
+ }
+
+ /**
* Apply a bounds change to a task.
* @param windowDecoration decor of task we are changing bounds for
* @param taskBounds new bounds of this task
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 9082323..917abf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -21,8 +21,6 @@
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
-import androidx.annotation.Nullable;
-
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -42,28 +40,31 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes in this region, the positioner will not attempt to
+ // If a task move (not resize) finishes with the positions y less than this value, do not
// finalize the bounds there using WCT#setBounds
- private final Rect mDisallowedAreaForEndBounds;
+ private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private int mCtrlType;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) {
- this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
- dragStartListener -> {}, SurfaceControl.Transaction::new);
+ DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
+ SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
}
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier) {
+ Supplier<SurfaceControl.Transaction> supplier,
+ int disallowedAreaForEndBoundsHeight) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
- mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
+ mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
+ mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
}
@Override
@@ -121,10 +122,10 @@
}
mTaskOrganizer.applyTransaction(wct);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
+ && y > mDisallowedAreaForEndBoundsHeight) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+ DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 39b9021..bf3ff3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -53,33 +53,35 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes in this region, the positioner will not attempt to
+ // If a task move (not resize) finishes with the positions y less than this value, do not
// finalize the bounds there using WCT#setBounds
- private final Rect mDisallowedAreaForEndBounds;
+ private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
- Rect disallowedAreaForEndBounds,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions) {
- this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
- dragStartListener, SurfaceControl.Transaction::new, transitions);
+ Transitions transitions,
+ int disallowedAreaForEndBoundsHeight) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener,
+ SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
- Rect disallowedAreaForEndBounds,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
+ int disallowedAreaForEndBoundsHeight) {
mTaskOrganizer = taskOrganizer;
mDesktopWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
- mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
mTransactionSupplier = supplier;
mTransitions = transitions;
+ mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
+ mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
}
@Override
@@ -110,8 +112,7 @@
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
- mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t,
- x, y);
+ mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
t.apply();
}
}
@@ -138,9 +139,9 @@
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
}
- } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
- DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+ } else if (y > mDisallowedAreaForEndBoundsHeight) {
+ DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 23158ea..adc2a6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -90,6 +90,7 @@
@Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
@Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
@Mock private SurfaceControl.Transaction mTransaction;
+ @Mock private Display mDisplay;
private final List<InputManager> mMockInputManagers = new ArrayList<>();
private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
@@ -126,6 +127,9 @@
final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
inputChannels[0].dispose();
when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
+
+ mDesktopModeWindowDecoration.mDisplay = mDisplay;
+ doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 69604dd..6f0599a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -22,6 +22,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
@@ -71,16 +72,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- taskPositioner =
- FluidResizeTaskPositioner(
- mockShellTaskOrganizer,
- mockWindowDecoration,
- mockDisplayController,
- DISALLOWED_AREA_FOR_END_BOUNDS,
- mockDragStartListener,
- mockTransactionFactory
- )
-
whenever(taskToken.asBinder()).thenReturn(taskBinder)
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -101,6 +92,15 @@
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+ taskPositioner = FluidResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockWindowDecoration,
+ mockDisplayController,
+ mockDragStartListener,
+ mockTransactionFactory,
+ DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ )
}
@Test
@@ -544,7 +544,7 @@
)
val newX = STARTING_BOUNDS.right.toFloat() + 5
- val newY = STARTING_BOUNDS.top.toFloat() + 5
+ val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -614,6 +614,38 @@
})
}
+ @Test
+ fun testDragResize_drag_taskPositionedInStableBounds() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = STARTING_BOUNDS.left.toFloat()
+ val newY = STABLE_BOUNDS.top.toFloat() - 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+ // but not in disallowed end bounds area.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ STABLE_BOUNDS.top
+ }
+ })
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -622,10 +654,11 @@
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
private const val NAVBAR_HEIGHT = 50
+ private const val CAPTION_HEIGHT = 50
+ private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
- private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
private val STABLE_INSETS = Rect(0, 50, 0, 0)
- private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300)
private val DISALLOWED_RESIZE_AREA = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
@@ -633,7 +666,7 @@
DISPLAY_BOUNDS.bottom)
private val STABLE_BOUNDS = Rect(
DISPLAY_BOUNDS.left,
- DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 4147dd8..3465ddd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -89,17 +89,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- taskPositioner =
- VeiledResizeTaskPositioner(
- mockShellTaskOrganizer,
- mockDesktopWindowDecoration,
- mockDisplayController,
- DISALLOWED_AREA_FOR_END_BOUNDS,
- mockDragStartListener,
- mockTransactionFactory,
- mockTransitions
- )
-
whenever(taskToken.asBinder()).thenReturn(taskBinder)
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -119,6 +108,17 @@
}
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+ taskPositioner =
+ VeiledResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockDesktopWindowDecoration,
+ mockDisplayController,
+ mockDragStartListener,
+ mockTransactionFactory,
+ mockTransitions,
+ DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ )
}
@Test
@@ -269,7 +269,7 @@
)
val newX = STARTING_BOUNDS.left.toFloat() + 5
- val newY = STARTING_BOUNDS.top.toFloat() + 5
+ val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -334,6 +334,38 @@
})
}
+ @Test
+ fun testDragResize_drag_taskPositionedInStableBounds() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = STARTING_BOUNDS.left.toFloat()
+ val newY = STABLE_BOUNDS.top.toFloat() - 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+ // but not in disallowed end bounds area.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ STABLE_BOUNDS.top
+ }
+ })
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -342,12 +374,13 @@
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
private const val NAVBAR_HEIGHT = 50
+ private const val CAPTION_HEIGHT = 50
+ private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
- private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
- private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50)
+ private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
private val STABLE_BOUNDS = Rect(
DISPLAY_BOUNDS.left,
- DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 00919dc..f71e728 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -79,7 +79,7 @@
void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
ATRACE_NAME("initShaderDiskCache");
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
// Emulators can switch between different renders either as part of config
// or snapshot migration. Also, program binaries may not work well on some
@@ -92,7 +92,7 @@
}
void ShaderCache::setFilename(const char* filename) {
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
mFilename = filename;
}
@@ -104,7 +104,7 @@
sk_sp<SkData> ShaderCache::load(const SkData& key) {
ATRACE_NAME("ShaderCache::load");
size_t keySize = key.size();
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
if (!mInitialized) {
return nullptr;
}
@@ -181,13 +181,18 @@
auto key = sIDKey;
set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
}
+ // The most straightforward way to make ownership shared
+ mMutex.unlock();
+ mMutex.lock_shared();
mBlobCache->writeToFile();
+ mMutex.unlock_shared();
+ mMutex.lock();
}
}
void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
ATRACE_NAME("ShaderCache::store");
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
mNumShadersCachedInRam++;
ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
@@ -229,7 +234,7 @@
mSavePending = true;
std::thread deferredSaveThread([this]() {
usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
// Store file on disk if there a new shader or Vulkan pipeline cache size changed.
if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
saveToDiskLocked();
@@ -245,11 +250,12 @@
void ShaderCache::onVkFrameFlushed(GrDirectContext* context) {
{
- std::lock_guard<std::mutex> lock(mMutex);
-
+ mMutex.lock_shared();
if (!mInitialized || !mTryToStorePipelineCache) {
+ mMutex.unlock_shared();
return;
}
+ mMutex.unlock_shared();
}
mInStoreVkPipelineInProgress = true;
context->storeVkPipelineCacheData();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index f5506d6..2f91c77 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -19,8 +19,10 @@
#include <GrContextOptions.h>
#include <SkRefCnt.h>
#include <cutils/compiler.h>
+#include <ftl/shared_mutex.h>
+#include <utils/Mutex.h>
+
#include <memory>
-#include <mutex>
#include <string>
#include <vector>
@@ -99,20 +101,20 @@
* this will do so, loading the serialized cache contents from disk if
* possible.
*/
- BlobCache* getBlobCacheLocked();
+ BlobCache* getBlobCacheLocked() REQUIRES(mMutex);
/**
* "validateCache" updates the cache to match the given identity. If the
* cache currently has the wrong identity, all entries in the cache are cleared.
*/
- bool validateCache(const void* identity, ssize_t size);
+ bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
/**
- * "saveToDiskLocked" attemps to save the current contents of the cache to
+ * "saveToDiskLocked" attempts to save the current contents of the cache to
* disk. If the identity hash exists, we will insert the identity hash into
* the cache for next validation.
*/
- void saveToDiskLocked();
+ void saveToDiskLocked() REQUIRES(mMutex);
/**
* "mInitialized" indicates whether the ShaderCache is in the initialized
@@ -122,7 +124,7 @@
* the load and store methods will return without performing any cache
* operations.
*/
- bool mInitialized = false;
+ bool mInitialized GUARDED_BY(mMutex) = false;
/**
* "mBlobCache" is the cache in which the key/value blob pairs are stored. It
@@ -131,7 +133,7 @@
* The blob cache contains the Android build number. We treat version mismatches as an empty
* cache (logic implemented in BlobCache::unflatten).
*/
- std::unique_ptr<FileBlobCache> mBlobCache;
+ std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
/**
* "mFilename" is the name of the file for storing cache contents in between
@@ -140,7 +142,7 @@
* empty string indicates that the cache should not be saved to or restored
* from disk.
*/
- std::string mFilename;
+ std::string mFilename GUARDED_BY(mMutex);
/**
* "mIDHash" is the current identity hash for the cache validation. It is
@@ -149,7 +151,7 @@
* indicates that cache validation is not performed, and the hash should
* not be stored on disk.
*/
- std::vector<uint8_t> mIDHash;
+ std::vector<uint8_t> mIDHash GUARDED_BY(mMutex);
/**
* "mSavePending" indicates whether or not a deferred save operation is
@@ -159,7 +161,7 @@
* contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving
* is disabled.
*/
- bool mSavePending = false;
+ bool mSavePending GUARDED_BY(mMutex) = false;
/**
* "mObservedBlobValueSize" is the maximum value size observed by the cache reading function.
@@ -174,16 +176,16 @@
unsigned int mDeferredSaveDelayMs = 4 * 1000;
/**
- * "mMutex" is the mutex used to prevent concurrent access to the member
+ * "mMutex" is the shared mutex used to prevent concurrent access to the member
* variables. It must be locked whenever the member variables are accessed.
*/
- mutable std::mutex mMutex;
+ mutable ftl::SharedMutex mMutex;
/**
* If set to "true", the next call to onVkFrameFlushed, will invoke
* GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk.
*/
- bool mTryToStorePipelineCache = true;
+ bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true;
/**
* This flag is used by "ShaderCache::store" to distinguish between shader data and
@@ -195,16 +197,16 @@
* "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used
* to prevent unnecessary disk writes, if the pipeline cache size has not changed.
*/
- size_t mNewPipelineCacheSize = -1;
+ size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1;
/**
* "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk.
*/
- size_t mOldPipelineCacheSize = -1;
+ size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1;
/**
* "mCacheDirty" is true when there is new shader cache data, which is not saved to disk.
*/
- bool mCacheDirty = false;
+ bool mCacheDirty GUARDED_BY(mMutex) = false;
/**
* "sCache" is the singleton ShaderCache object.
@@ -221,7 +223,7 @@
* interesting to keep track of how many shaders are stored in RAM. This
* class provides a convenient entry point for that.
*/
- int mNumShadersCachedInRam = 0;
+ int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0;
friend class ShaderCacheTestUtils; // used for unit testing
};
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 7bcd45c..9aa2e1d 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -49,7 +49,7 @@
*/
static void reinitializeAllFields(ShaderCache& cache) {
ShaderCache newCache = ShaderCache();
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex);
// By order of declaration
cache.mInitialized = newCache.mInitialized;
cache.mBlobCache.reset(nullptr);
@@ -72,7 +72,7 @@
* manually, as seen in the "terminate" testing helper function.
*/
static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
cache.mDeferredSaveDelayMs = saveDelayMs;
}
@@ -81,7 +81,7 @@
* Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
*/
static void terminate(ShaderCache& cache, bool saveContent) {
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
if (saveContent) {
cache.saveToDiskLocked();
}
@@ -93,6 +93,7 @@
*/
template <typename T>
static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
+ std::lock_guard lock(cache.mMutex);
return cache.validateCache(hash.data(), hash.size() * sizeof(T));
}
@@ -108,7 +109,7 @@
*/
static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
{
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
ASSERT_TRUE(cache.mSavePending);
}
bool saving = true;
@@ -123,7 +124,7 @@
usleep(delayMicroseconds);
elapsedMilliseconds += (float)delayMicroseconds / 1000;
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
saving = cache.mSavePending;
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 48259e1..9da1ab8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -38,6 +38,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
@@ -238,6 +239,7 @@
// If we fail to apply the setting, by definition nothing happened
sendBroadcast = false;
sendBroadcastSystemUI = false;
+ Log.e(TAG, "Failed to restore setting name: " + name + " + value: " + value, e);
} finally {
// If this was an element of interest, send the "we just restored it"
// broadcast with the historical value now that the new value has
diff --git a/packages/SystemUI/res/drawable/stat_sys_connected_display.xml b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
new file mode 100644
index 0000000..3f3d6f5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:fillColor="@android:color/white"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M320,840L320,760L400,760L400,680L160,680Q127,680 103.5,656.5Q80,633 80,600L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,600Q880,633 856.5,656.5Q833,680 800,680L560,680L560,760L640,760L640,840L320,840ZM160,600L800,600Q800,600 800,600Q800,600 800,600L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600ZM160,600Q160,600 160,600Q160,600 160,600L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600L160,600Z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 3e16d55..6f59684 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -43,6 +43,15 @@
private long mPinLength;
private boolean mDisabledAutoConfirmation;
+ /**
+ * Responsible for identifying if PIN hinting is to be enabled or not
+ */
+ private boolean mIsPinHinting;
+
+ /**
+ * Responsible for identifying if auto confirm is enabled or not in Settings
+ */
+ private boolean mIsAutoPinConfirmEnabledInSettings;
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -63,6 +72,9 @@
mFeatureFlags = featureFlags;
mBackspaceKey = view.findViewById(R.id.delete_button);
mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
+ mIsPinHinting = mPinLength == DEFAULT_PIN_LENGTH;
+ mIsAutoPinConfirmEnabledInSettings = mLockPatternUtils.isAutoPinConfirmEnabled(
+ KeyguardUpdateMonitor.getCurrentUser());
}
@Override
@@ -82,7 +94,7 @@
protected void onUserInput() {
super.onUserInput();
- if (isAutoPinConfirmEnabledInSettings()) {
+ if (mIsAutoPinConfirmEnabledInSettings) {
updateAutoConfirmationState();
if (mPasswordEntry.getText().length() == mPinLength
&& mOkButton.getVisibility() == View.INVISIBLE) {
@@ -130,7 +142,7 @@
* Updates the visibility of the OK button for auto confirm feature
*/
private void updateOKButtonVisibility() {
- if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) {
+ if (mIsPinHinting && !mDisabledAutoConfirmation) {
mOkButton.setVisibility(View.INVISIBLE);
} else {
mOkButton.setVisibility(View.VISIBLE);
@@ -142,10 +154,9 @@
* Visibility changes are only for auto confirmation configuration.
*/
private void updateBackSpaceVisibility() {
- boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings();
mBackspaceKey.setTransparentMode(/* isTransparentMode= */
- isAutoConfirmation && !mDisabledAutoConfirmation);
- if (isAutoConfirmation) {
+ mIsAutoPinConfirmEnabledInSettings && !mDisabledAutoConfirmation);
+ if (mIsAutoPinConfirmEnabledInSettings) {
if (mPasswordEntry.getText().length() > 0
|| mDisabledAutoConfirmation) {
mBackspaceKey.setVisibility(View.VISIBLE);
@@ -155,24 +166,8 @@
}
}
/** Updates whether to use pin hinting or not. */
- void updatePinHinting() {
- mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting()
+ private void updatePinHinting() {
+ mPasswordEntry.setIsPinHinting(mIsAutoPinConfirmEnabledInSettings && mIsPinHinting
&& !mDisabledAutoConfirmation);
}
-
- /**
- * Responsible for identifying if PIN hinting is to be enabled or not
- */
- private boolean isPinHinting() {
- return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
- == DEFAULT_PIN_LENGTH;
- }
-
- /**
- * Responsible for identifying if auto confirm is enabled or not in Settings
- */
- private boolean isAutoPinConfirmEnabledInSettings() {
- //Checks if user has enabled the auto confirm in Settings
- return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 841b5b3..6853f81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -281,6 +281,8 @@
public interface SwipeListener {
void onSwipeUp();
+ /** */
+ void onSwipeDown();
}
@VisibleForTesting
@@ -543,6 +545,11 @@
if (mSwipeListener != null) {
mSwipeListener.onSwipeUp();
}
+ } else if (getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
+ if (mSwipeListener != null) {
+ mSwipeListener.onSwipeDown();
+ }
}
}
return true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 7c511a3..880f242 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -70,11 +70,11 @@
import com.android.systemui.R;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
@@ -319,6 +319,11 @@
"swipeUpOnBouncer");
}
}
+
+ @Override
+ public void onSwipeDown() {
+ mViewMediatorCallback.onBouncerSwipeDown();
+ }
};
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 50f8f7e..14ec27a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -104,4 +104,9 @@
* Call when cancel button is pressed in bouncer.
*/
void onCancelClicked();
+
+ /**
+ * Determines if bouncer has swiped down.
+ */
+ void onBouncerSwipeDown();
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 35830da..0b25184 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -19,17 +19,22 @@
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
import dagger.Binds
import dagger.Module
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.withContext
import java.util.function.Function
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Defines interface for classes that can access authentication-related application state. */
interface AuthenticationRepository {
@@ -64,9 +69,6 @@
*/
suspend fun getAuthenticationMethod(): AuthenticationMethodModel
- /** See [isUnlocked]. */
- fun setUnlocked(isUnlocked: Boolean)
-
/** See [isBypassEnabled]. */
fun setBypassEnabled(isBypassEnabled: Boolean)
@@ -77,14 +79,20 @@
class AuthenticationRepositoryImpl
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
+ keyguardRepository: KeyguardRepository,
) : AuthenticationRepository {
- private val _isUnlocked = MutableStateFlow(false)
- override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+ override val isUnlocked: StateFlow<Boolean> =
+ keyguardRepository.isKeyguardUnlocked.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
@@ -128,10 +136,6 @@
}
}
- override fun setUnlocked(isUnlocked: Boolean) {
- _isUnlocked.value = isUnlocked
- }
-
override fun setBypassEnabled(isBypassEnabled: Boolean) {
_isBypassEnabled.value = isBypassEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 9ae27556..15e579d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -90,22 +90,6 @@
}
/**
- * Unlocks the device, assuming that the authentication challenge has been completed
- * successfully.
- */
- fun unlockDevice() {
- repository.setUnlocked(true)
- }
-
- /**
- * Locks the device. From now on, the device will remain locked until [authenticate] is called
- * with the correct input.
- */
- fun lockDevice() {
- repository.setUnlocked(false)
- }
-
- /**
* Attempts to authenticate the user and unlock the device.
*
* If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
@@ -146,7 +130,6 @@
if (isSuccessful) {
repository.setFailedAuthenticationAttempts(0)
- repository.setUnlocked(true)
} else {
repository.setFailedAuthenticationAttempts(
repository.failedAuthenticationAttempts.value + 1
@@ -156,12 +139,6 @@
return isSuccessful
}
- /** Triggers a biometric-powered unlock of the device. */
- fun biometricUnlock() {
- // TODO(b/280883900): only allow this if the biometric is enabled and there's a match.
- repository.setUnlocked(true)
- }
-
/** See [isBypassEnabled]. */
fun toggleBypassEnabled() {
repository.setBypassEnabled(!repository.isBypassEnabled.value)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 0dc7974..dc9ba87 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -75,6 +75,8 @@
import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
import com.android.systemui.biometrics.udfps.TouchProcessor;
import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
@@ -82,9 +84,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -189,6 +189,8 @@
@Nullable private VelocityTracker mVelocityTracker;
// The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
private int mActivePointerId = -1;
+ // Whether a pointer has been pilfered for current gesture
+ private boolean mPointerPilfered = false;
// The timestamp of the most recent touch log.
private long mTouchLogTime;
// The timestamp of the most recent log of a touch InteractionEvent.
@@ -258,10 +260,6 @@
@Override
public void showUdfpsOverlay(long requestId, int sensorId, int reason,
@NonNull IUdfpsOverlayControllerCallback callback) {
- if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
- return;
- }
-
mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
mWindowManager, mAccessibilityManager, mStatusBarStateController,
@@ -279,10 +277,6 @@
@Override
public void hideUdfpsOverlay(int sensorId) {
- if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
- return;
- }
-
mFgExecutor.execute(() -> {
if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
// if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
@@ -560,6 +554,11 @@
|| mPrimaryBouncerInteractor.isInTransit()) {
return false;
}
+ if (event.getAction() == MotionEvent.ACTION_DOWN
+ || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ // Reset on ACTION_DOWN, start of new gesture
+ mPointerPilfered = false;
+ }
final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
mOverlayParams);
@@ -633,10 +632,11 @@
shouldPilfer = true;
}
- // Execute the pilfer
- if (shouldPilfer) {
+ // Pilfer only once per gesture
+ if (shouldPilfer && !mPointerPilfered) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+ mPointerPilfered = true;
}
return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index cb84162..5dd24b2 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -35,6 +35,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -72,24 +75,28 @@
val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
init {
+ // UNLOCKING SHOWS Gone.
+ //
+ // Move to the gone scene if the device becomes unlocked while on the bouncer scene.
applicationScope.launch {
- sceneInteractor.currentScene(containerName).collect { currentScene ->
- if (currentScene.key == SceneKey.Bouncer) {
- when (getAuthenticationMethod()) {
- is AuthenticationMethodModel.None ->
- sceneInteractor.setCurrentScene(
- containerName,
- SceneModel(SceneKey.Gone),
- )
- is AuthenticationMethodModel.Swipe ->
- sceneInteractor.setCurrentScene(
- containerName,
- SceneModel(SceneKey.Lockscreen),
- )
- else -> Unit
+ sceneInteractor
+ .currentScene(containerName)
+ .flatMapLatest { currentScene ->
+ if (currentScene.key == SceneKey.Bouncer) {
+ authenticationInteractor.isUnlocked
+ } else {
+ flowOf(false)
}
}
- }
+ .distinctUntilChanged()
+ .collect { isUnlocked ->
+ if (isUnlocked) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ }
+ }
}
}
@@ -119,7 +126,6 @@
scene = SceneModel(SceneKey.Bouncer),
)
} else {
- authenticationInteractor.unlockDevice()
sceneInteractor.setCurrentScene(
containerName = containerName,
scene = SceneModel(SceneKey.Gone),
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 7448f27..b293ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -22,17 +22,19 @@
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -83,25 +85,30 @@
}
/** View-model for the current UI, based on the current authentication method. */
- val authMethod: StateFlow<AuthMethodBouncerViewModel?>
- get() =
- flow {
- emit(null)
- emit(interactor.getAuthenticationMethod())
- }
- .map { authMethod ->
- when (authMethod) {
- is AuthenticationMethodModel.Pin -> pin
- is AuthenticationMethodModel.Password -> password
- is AuthenticationMethodModel.Pattern -> pattern
- else -> null
+ private val _authMethod =
+ MutableSharedFlow<AuthMethodBouncerViewModel?>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+ val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
+ _authMethod.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
+ init {
+ applicationScope.launch {
+ _authMethod.subscriptionCount
+ .pairwise()
+ .map { (previousCount, currentCount) -> currentCount > previousCount }
+ .collect { subscriberAdded ->
+ if (subscriberAdded) {
+ reloadAuthMethod()
}
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ }
+ }
/** The user-facing message to show in the bouncer. */
val message: StateFlow<MessageViewModel> =
@@ -184,6 +191,17 @@
)
}
+ private suspend fun reloadAuthMethod() {
+ _authMethod.tryEmit(
+ when (interactor.getAuthenticationMethod()) {
+ is AuthenticationMethodModel.Pin -> pin
+ is AuthenticationMethodModel.Password -> password
+ is AuthenticationMethodModel.Pattern -> pattern
+ else -> null
+ }
+ )
+ }
+
data class MessageViewModel(
val text: String,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 7c96e82..014ebc3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -42,10 +42,7 @@
/** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
val hintedPinLength: StateFlow<Int?> =
- flow {
- emit(null)
- emit(interactor.getAuthenticationMethod())
- }
+ flow { emit(interactor.getAuthenticationMethod()) }
.map { authMethod ->
// Hinting is enabled for 6-digit codes only
autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 9ab9e7a..8f3c3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -47,6 +47,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
import com.android.systemui.demomode.dagger.DemoModeModule;
+import com.android.systemui.display.DisplayModule;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
import com.android.systemui.dump.DumpManager;
@@ -163,6 +164,7 @@
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommonRepositoryModule.class,
+ DisplayModule.class,
ConnectivityModule.class,
CoroutinesModule.class,
DreamModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
new file mode 100644
index 0000000..65cd84b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display
+
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+/** Module binding display related classes. */
+@Module
+interface DisplayModule {
+ @Binds
+ fun bindConnectedDisplayInteractor(
+ provider: ConnectedDisplayInteractorImpl
+ ): ConnectedDisplayInteractor
+
+ @Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
new file mode 100644
index 0000000..c962e51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.DisplayMetrics
+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.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.DisplayMetricsRepoLog
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository tracking display-related metrics like display height and width. */
+@SysUISingleton
+class DisplayMetricsRepository
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ configurationController: ConfigurationController,
+ displayMetricsHolder: DisplayMetrics,
+ context: Context,
+ @DisplayMetricsRepoLog logBuffer: LogBuffer,
+) {
+
+ private val displayMetrics: StateFlow<DisplayMetrics> =
+ conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ context.display.getMetrics(displayMetricsHolder)
+ trySend(displayMetricsHolder)
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+ .onEach {
+ logBuffer.log(
+ "DisplayMetrics",
+ LogLevel.INFO,
+ { str1 = it.toString() },
+ { "New metrics: $str1" },
+ )
+ }
+ .stateIn(scope, SharingStarted.Eagerly, displayMetricsHolder)
+
+ /** Returns the current display height in pixels. */
+ val heightPixels: Int
+ get() = displayMetrics.value.heightPixels
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
new file mode 100644
index 0000000..4b957c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.domain.interactor
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** Provides information about an external connected display. */
+interface ConnectedDisplayInteractor {
+ /**
+ * Provides the current external display state.
+ *
+ * The state is:
+ * - [State.CONNECTED] when there is at least one display with [TYPE_EXTERNAL].
+ * - [State.CONNECTED_SECURE] when is at least one display with both [TYPE_EXTERNAL] AND
+ * [Display.FLAG_SECURE] set
+ */
+ val connectedDisplayState: Flow<State>
+
+ /** Possible connected display state. */
+ enum class State {
+ DISCONNECTED,
+ CONNECTED,
+ CONNECTED_SECURE,
+ }
+}
+
+@SysUISingleton
+class ConnectedDisplayInteractorImpl
+@Inject
+constructor(
+ displayRepository: DisplayRepository,
+) : ConnectedDisplayInteractor {
+
+ override val connectedDisplayState: Flow<State> =
+ displayRepository.displays
+ .map { displays ->
+ val externalDisplays =
+ displays.filter { display -> display.type == Display.TYPE_EXTERNAL }
+
+ val secureExternalDisplays =
+ externalDisplays.filter { it.flags and Display.FLAG_SECURE != 0 }
+
+ if (externalDisplays.isEmpty()) {
+ State.DISCONNECTED
+ } else if (!secureExternalDisplays.isEmpty()) {
+ State.CONNECTED_SECURE
+ } else {
+ State.CONNECTED
+ }
+ }
+ .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6242492..db5f546 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -151,12 +151,6 @@
// TODO(b/255607168): Tracking Bug
@JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
- // TODO(b/252897742): Tracking Bug
- @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection")
-
- // TODO(b/252897742): Tracking Bug
- @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay")
-
/**
* Whether to enable the code powering customizable lock screen quick affordances.
*
@@ -730,6 +724,11 @@
val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
releasedFlag(2805, "split_shade_subpixel_optimization")
+ // TODO(b/288868056): Tracking Bug
+ @JvmField
+ val PARTIAL_SCREEN_SHARING_TASK_SWITCHER =
+ unreleasedFlag(288868056, "pss_task_switcher")
+
// TODO(b/278761837): Tracking Bug
@JvmField
val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c629ebf..155e023 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -835,6 +835,11 @@
}
@Override
+ public void onBouncerSwipeDown() {
+ mKeyguardViewControllerLazy.get().reset(/* hideBouncerWhenShowing= */ true);
+ }
+
+ @Override
public void playTrustedSound() {
KeyguardViewMediator.this.playTrustedSound();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 81f62b6..edc0b45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -302,7 +302,15 @@
trySendWithFailureLogging(
keyguardStateController.isUnlocked,
TAG,
- "updated isKeyguardUnlocked"
+ "updated isKeyguardUnlocked due to onUnlockedChanged"
+ )
+ }
+
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
index 7bbc0d6..c8f7efb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -23,7 +23,6 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -111,24 +110,6 @@
}
}
}
-
- // SWIPE TO DISMISS Lockscreen.
- //
- // If switched from the lockscreen to the gone scene and the auth method was a swipe,
- // unlocks the device.
- applicationScope.launch {
- sceneInteractor.currentScene(containerName).pairwise().collect {
- (previousScene, currentScene) ->
- if (
- authenticationInteractor.getAuthenticationMethod() is
- AuthenticationMethodModel.Swipe &&
- previousScene.key == SceneKey.Lockscreen &&
- currentScene.key == SceneKey.Gone
- ) {
- authenticationInteractor.unlockDevice()
- }
- }
- }
}
/** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
new file mode 100644
index 0000000..fa9ec88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for display metrics related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class DisplayMetricsRepoLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 3497285..b5759e3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -488,4 +488,12 @@
public static LogBuffer provideDreamLogBuffer(LogBufferFactory factory) {
return factory.create("DreamLog", 250);
}
+
+ /** Provides a {@link LogBuffer} for display metrics related logs. */
+ @Provides
+ @SysUISingleton
+ @DisplayMetricsRepoLog
+ public static LogBuffer provideDisplayMetricsRepoLogBuffer(LogBufferFactory factory) {
+ return factory.create("DisplayMetricsRepo", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index c70cce9..2fafba1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -120,6 +120,7 @@
val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
if (tilesToRemove.isNotEmpty()) {
+ Log.d(TAG, "Removing tiles: $tilesToRemove")
qsHost.removeTiles(tilesToRemove)
}
val tiles = synchronized(autoAdded) {
@@ -255,6 +256,7 @@
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("Current user: $userId")
+ pw.println("Restored tiles: $restoredTiles")
pw.println("Added tiles: $autoAdded")
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index d2568ac..432147f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -152,6 +152,7 @@
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
mUserTracker = userTracker;
+ mCurrentUser = userTracker.getUserId();
mSecureSettings = secureSettings;
mCustomTileStatePersister = customTileStatePersister;
@@ -161,7 +162,9 @@
// finishes before creating any tiles.
tunerService.addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
- mAutoTiles = autoTiles.get();
+ if (!mFeatureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
+ mAutoTiles = autoTiles.get();
+ }
});
}
@@ -272,6 +275,13 @@
if (!TILES_SETTING.equals(key)) {
return;
}
+ int currentUser = mUserTracker.getUserId();
+ if (currentUser != mCurrentUser) {
+ mUserContext = mUserTracker.getUserContext();
+ if (mAutoTiles != null) {
+ mAutoTiles.changeUser(UserHandle.of(currentUser));
+ }
+ }
// Do not process tiles if the flag is enabled.
if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
return;
@@ -280,13 +290,6 @@
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
- int currentUser = mUserTracker.getUserId();
- if (currentUser != mCurrentUser) {
- mUserContext = mUserTracker.getUserContext();
- if (mAutoTiles != null) {
- mAutoTiles.changeUser(UserHandle.of(currentUser));
- }
- }
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
Log.d(TAG, "Recreating tiles: " + tileSpecs);
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
@@ -301,7 +304,7 @@
if (tile != null && (!(tile instanceof CustomTile)
|| ((CustomTile) tile).getUser() == currentUser)) {
if (tile.isAvailable()) {
- if (DEBUG) Log.d(TAG, "Adding " + tile);
+ Log.d(TAG, "Adding " + tile);
tile.removeCallbacks();
if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
tile.userSwitch(currentUser);
@@ -420,6 +423,7 @@
// When calling this, you may want to modify mTilesListDirty accordingly.
@MainThread
private void saveTilesToSettings(List<String> tileSpecs) {
+ Log.d(TAG, "Saving tiles: " + tileSpecs + " for user: " + mCurrentUser);
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
@@ -493,7 +497,7 @@
lifecycleManager.flushMessagesAndUnbind();
}
}
- if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
+ Log.d(TAG, "saveCurrentTiles " + newTiles);
mTilesListDirty = true;
saveTilesToSettings(newTiles);
}
@@ -564,9 +568,9 @@
if (TextUtils.isEmpty(tileList)) {
tileList = res.getString(R.string.quick_settings_tiles);
- if (DEBUG) Log.d(TAG, "Loaded tile specs from default config: " + tileList);
+ Log.d(TAG, "Loaded tile specs from default config: " + tileList);
} else {
- if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
+ Log.d(TAG, "Loaded tile specs from setting: " + tileList);
}
final ArrayList<String> tiles = new ArrayList<String>();
boolean addedDefault = false;
@@ -612,6 +616,10 @@
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("QSTileHost:");
+ pw.println("tile specs: " + mTileSpecs);
+ pw.println("current user: " + mCurrentUser);
+ pw.println("is dirty: " + mTilesListDirty);
+ pw.println("tiles:");
mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
.forEach(o -> ((Dumpable) o).dump(pw, args));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index dffe7fd..03de3a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -19,9 +19,9 @@
import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
import android.content.Context;
-import android.hardware.display.NightDisplayListener;
import android.os.Handler;
+import com.android.systemui.dagger.NightDisplayListenerModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dagger.MediaModule;
@@ -41,14 +41,14 @@
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
import java.util.Map;
import javax.inject.Named;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.Multibinds;
+
/**
* Module for QS dependencies
*/
@@ -79,7 +79,7 @@
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
- NightDisplayListener nightDisplayListener,
+ NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
@@ -95,7 +95,7 @@
hotspotController,
dataSaverController,
managedProfileController,
- nightDisplayListener,
+ nightDisplayListenerBuilder,
castController,
reduceBrightColorsController,
deviceControlsController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
new file mode 100644
index 0000000..adea26e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
+import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DataSaverAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DeviceControlsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.HotspotAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.NightDisplayAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.ReduceBrightColorsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WalletAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WorkTileAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import dagger.multibindings.IntoSet
+
+@Module
+interface BaseAutoAddableModule {
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun providesAutoAddableSetting(
+ @Main resources: Resources,
+ autoAddableSettingFactory: AutoAddableSetting.Factory
+ ): Set<AutoAddable> {
+ return AutoAddableSettingList.parseSettingsResource(
+ resources,
+ autoAddableSettingFactory
+ )
+ .toSet()
+ }
+ }
+
+ @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
+
+ @Binds @IntoSet fun bindDataSaverAutoAddable(impl: DataSaverAutoAddable): AutoAddable
+
+ @Binds @IntoSet fun bindDeviceControlsAutoAddable(impl: DeviceControlsAutoAddable): AutoAddable
+
+ @Binds @IntoSet fun bindHotspotAutoAddable(impl: HotspotAutoAddable): AutoAddable
+
+ @Binds @IntoSet fun bindNightDisplayAutoAddable(impl: NightDisplayAutoAddable): AutoAddable
+
+ @Binds
+ @IntoSet
+ fun bindReduceBrightColorsAutoAddable(impl: ReduceBrightColorsAutoAddable): AutoAddable
+
+ @Binds @IntoSet fun bindWalletAutoAddable(impl: WalletAutoAddable): AutoAddable
+
+ @Binds @IntoSet fun bindWorkModeAutoAddable(impl: WorkTileAutoAddable): AutoAddable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
new file mode 100644
index 0000000..91cb5bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for the QS pipeline to track auto-added tiles */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSAutoAddLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
index 9979228..a010ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
@@ -16,13 +16,40 @@
package com.android.systemui.qs.pipeline.dagger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
import com.android.systemui.qs.pipeline.data.repository.AutoAddSettingRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import dagger.multibindings.Multibinds
-@Module
+@Module(
+ includes =
+ [
+ BaseAutoAddableModule::class,
+ ]
+)
abstract class QSAutoAddModule {
@Binds abstract fun bindAutoAddRepository(impl: AutoAddSettingRepository): AutoAddRepository
+
+ @Multibinds abstract fun providesAutoAddableSet(): Set<AutoAddable>
+
+ companion object {
+ /**
+ * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
+ * auto added tiles.
+ */
+ @Provides
+ @SysUISingleton
+ @QSAutoAddLog
+ fun provideQSAutoAddLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create(QSPipelineLogger.AUTO_ADD_TAG, maxSize = 100, systrace = false)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index d7ae575..a4600fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -26,7 +26,7 @@
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
-import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
+import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import dagger.Binds
import dagger.Module
@@ -53,8 +53,8 @@
@Binds
@IntoMap
- @ClassKey(PrototypeCoreStartable::class)
- abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
+ @ClassKey(QSPipelineCoreStartable::class)
+ abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
index ad8bfea..c56ca8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
@@ -19,5 +19,5 @@
import java.lang.annotation.RetentionPolicy
import javax.inject.Qualifier
-/** A {@link LogBuffer} for the new QS Pipeline for logging changes to the set of current tiles. */
+/** A [LogBuffer] for the new QS Pipeline for logging changes to the set of current tiles. */
@Qualifier @MustBeDocumented @Retention(RetentionPolicy.RUNTIME) annotation class QSTileListLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt
new file mode 100644
index 0000000..45129b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * It tracks a specific `Secure` int [setting] and when its value changes to non-zero, it will emit
+ * a [AutoAddSignal.Add] for [spec].
+ */
+class AutoAddableSetting
+@AssistedInject
+constructor(
+ private val secureSettings: SecureSettings,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Assisted private val setting: String,
+ @Assisted private val spec: TileSpec,
+) : AutoAddable {
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return secureSettings
+ .observerFlow(userId, setting)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getIntForUser(setting, 0, userId) != 0 }
+ .distinctUntilChanged()
+ .filter { it }
+ .map { AutoAddSignal.Add(spec) }
+ .flowOn(bgDispatcher)
+ }
+
+ override val autoAddTracking = AutoAddTracking.IfNotAdded(spec)
+
+ override val description = "AutoAddableSetting: $setting:$spec ($autoAddTracking)"
+
+ override fun equals(other: Any?): Boolean {
+ return other is AutoAddableSetting && spec == other.spec && setting == other.setting
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(spec, setting)
+ }
+
+ override fun toString(): String {
+ return description
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(setting: String, spec: TileSpec): AutoAddableSetting
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
new file mode 100644
index 0000000..b1c7433
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.res.Resources
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object AutoAddableSettingList {
+
+ /** Parses [R.array.config_quickSettingsAutoAdd] into a collection of [AutoAddableSetting]. */
+ fun parseSettingsResource(
+ resources: Resources,
+ autoAddableSettingFactory: AutoAddableSetting.Factory,
+ ): Iterable<AutoAddable> {
+ val autoAddList = resources.getStringArray(R.array.config_quickSettingsAutoAdd)
+ return autoAddList.mapNotNull {
+ val elements = it.split(SETTING_SEPARATOR, limit = 2)
+ if (elements.size == 2) {
+ val setting = elements[0]
+ val spec = elements[1]
+ val tileSpec = TileSpec.create(spec)
+ if (tileSpec == TileSpec.Invalid) {
+ Log.w(TAG, "Malformed item in array: $it")
+ null
+ } else {
+ autoAddableSettingFactory.create(setting, TileSpec.create(spec))
+ }
+ } else {
+ Log.w(TAG, "Malformed item in array: $it")
+ null
+ }
+ }
+ }
+
+ private const val SETTING_SEPARATOR = ":"
+ private const val TAG = "AutoAddableSettingList"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
new file mode 100644
index 0000000..88a49ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Generic [AutoAddable] for tiles that are added based on a signal from a [CallbackController]. */
+abstract class CallbackControllerAutoAddable<
+ Callback : Any, Controller : CallbackController<Callback>>(
+ private val controller: Controller,
+) : AutoAddable {
+
+ /** [TileSpec] for the tile to add. */
+ protected abstract val spec: TileSpec
+
+ /**
+ * Callback to be used to determine when to add the tile. When the callback determines that the
+ * feature has been enabled, it should call [sendAdd].
+ */
+ protected abstract fun ProducerScope<AutoAddSignal>.getCallback(): Callback
+
+ /** Sends an [AutoAddSignal.Add] for [spec]. */
+ protected fun ProducerScope<AutoAddSignal>.sendAdd() {
+ trySend(AutoAddSignal.Add(spec))
+ }
+
+ final override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return conflatedCallbackFlow {
+ val callback = getCallback()
+ controller.addCallback(callback)
+
+ awaitClose { controller.removeCallback(callback) }
+ }
+ }
+
+ override val autoAddTracking: AutoAddTracking
+ get() = AutoAddTracking.IfNotAdded(spec)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt
new file mode 100644
index 0000000..b5bef9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [CastTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there's a casting device connected or connecting.
+ */
+@SysUISingleton
+class CastAutoAddable
+@Inject
+constructor(
+ private val controller: CastController,
+) : CallbackControllerAutoAddable<CastController.Callback, CastController>(controller) {
+
+ override val spec: TileSpec
+ get() = TileSpec.create(CastTile.TILE_SPEC)
+
+ override fun ProducerScope<AutoAddSignal>.getCallback(): CastController.Callback {
+ return CastController.Callback {
+ val isCasting =
+ controller.castDevices.any {
+ it.state == CastController.CastDevice.STATE_CONNECTED ||
+ it.state == CastController.CastDevice.STATE_CONNECTING
+ }
+ if (isCasting) {
+ sendAdd()
+ }
+ }
+ }
+
+ override val description = "CastAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
new file mode 100644
index 0000000..a877aee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [DataSaverTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when data saver is enabled.
+ */
+@SysUISingleton
+class DataSaverAutoAddable
+@Inject
+constructor(
+ dataSaverController: DataSaverController,
+) :
+ CallbackControllerAutoAddable<DataSaverController.Listener, DataSaverController>(
+ dataSaverController
+ ) {
+
+ override val spec
+ get() = TileSpec.create(DataSaverTile.TILE_SPEC)
+
+ override fun ProducerScope<AutoAddSignal>.getCallback(): DataSaverController.Listener {
+ return DataSaverController.Listener { enabled ->
+ if (enabled) {
+ sendAdd()
+ }
+ }
+ }
+
+ override val description = "DataSaverAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
new file mode 100644
index 0000000..76bfad9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [DeviceControlsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when updating to a device that supports device controls. It
+ * will send a signal to remove the tile when the device does not support controls.
+ */
+@SysUISingleton
+class DeviceControlsAutoAddable
+@Inject
+constructor(
+ private val deviceControlsController: DeviceControlsController,
+) : AutoAddable {
+
+ private val spec = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return conflatedCallbackFlow {
+ val callback =
+ object : DeviceControlsController.Callback {
+ override fun onControlsUpdate(position: Int?) {
+ position?.let { trySend(AutoAddSignal.Add(spec, position)) }
+ deviceControlsController.removeCallback()
+ }
+
+ override fun removeControlsAutoTracker() {
+ trySend(AutoAddSignal.Remove(spec))
+ }
+ }
+
+ deviceControlsController.setCallback(callback)
+
+ awaitClose { deviceControlsController.removeCallback() }
+ }
+ }
+
+ override val autoAddTracking: AutoAddTracking
+ get() = AutoAddTracking.Always
+
+ override val description = "DeviceControlsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
new file mode 100644
index 0000000..9c59e12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [HotspotTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when hotspot is enabled.
+ */
+@SysUISingleton
+class HotspotAutoAddable
+@Inject
+constructor(
+ hotspotController: HotspotController,
+) :
+ CallbackControllerAutoAddable<HotspotController.Callback, HotspotController>(
+ hotspotController
+ ) {
+
+ override val spec
+ get() = TileSpec.create(HotspotTile.TILE_SPEC)
+
+ override fun ProducerScope<AutoAddSignal>.getCallback(): HotspotController.Callback {
+ return HotspotController.Callback { enabled, _ ->
+ if (enabled) {
+ sendAdd()
+ }
+ }
+ }
+
+ override val description = "HotspotAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
new file mode 100644
index 0000000..31ea734
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [NightDisplayTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when night display is enabled or when the auto mode changes
+ * to one that supports night display.
+ */
+@SysUISingleton
+class NightDisplayAutoAddable
+@Inject
+constructor(
+ private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+ context: Context,
+) : AutoAddable {
+
+ private val enabled = ColorDisplayManager.isNightDisplayAvailable(context)
+ private val spec = TileSpec.create(NightDisplayTile.TILE_SPEC)
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return conflatedCallbackFlow {
+ val nightDisplayListener = nightDisplayListenerBuilder.setUser(userId).build()
+
+ val callback =
+ object : NightDisplayListener.Callback {
+ override fun onActivated(activated: Boolean) {
+ if (activated) {
+ sendAdd()
+ }
+ }
+
+ override fun onAutoModeChanged(autoMode: Int) {
+ if (
+ autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME ||
+ autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT
+ ) {
+ sendAdd()
+ }
+ }
+
+ private fun sendAdd() {
+ trySend(AutoAddSignal.Add(spec))
+ }
+ }
+
+ nightDisplayListener.setCallback(callback)
+
+ awaitClose { nightDisplayListener.setCallback(null) }
+ }
+ }
+
+ override val autoAddTracking =
+ if (enabled) {
+ AutoAddTracking.IfNotAdded(spec)
+ } else {
+ AutoAddTracking.Disabled
+ }
+
+ override val description = "NightDisplayAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
new file mode 100644
index 0000000..267e2b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [ReduceBrightColorsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when reduce bright colors is enabled.
+ */
+@SysUISingleton
+class ReduceBrightColorsAutoAddable
+@Inject
+constructor(
+ controller: ReduceBrightColorsController,
+ @Named(RBC_AVAILABLE) private val available: Boolean,
+) :
+ CallbackControllerAutoAddable<
+ ReduceBrightColorsController.Listener, ReduceBrightColorsController
+ >(controller) {
+
+ override val spec: TileSpec
+ get() = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+
+ override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
+ return object : ReduceBrightColorsController.Listener {
+ override fun onActivated(activated: Boolean) {
+ if (activated) {
+ sendAdd()
+ }
+ }
+ }
+ }
+
+ override val autoAddTracking
+ get() =
+ if (available) {
+ super.autoAddTracking
+ } else {
+ AutoAddTracking.Disabled
+ }
+
+ override val description = "ReduceBrightColorsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
new file mode 100644
index 0000000..58a31bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.text.TextUtils
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.withContext
+
+/**
+ * [AutoAddable] for the safety tile.
+ *
+ * It will send a signal to add the tile when the feature is enabled, indicating the component
+ * corresponding to the tile. If the feature is disabled, it will send a signal to remove the tile.
+ */
+@SysUISingleton
+class SafetyCenterAutoAddable
+@Inject
+constructor(
+ private val safetyController: SafetyController,
+ private val packageManager: PackageManager,
+ @Main private val resources: Resources,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+) : AutoAddable {
+
+ private suspend fun getSpec(): TileSpec? {
+ val specClass = resources.getString(R.string.safety_quick_settings_tile_class)
+ return if (TextUtils.isEmpty(specClass)) {
+ null
+ } else {
+ val packageName =
+ withContext(bgDispatcher) { packageManager.permissionControllerPackageName }
+ TileSpec.create(ComponentName(packageName, specClass))
+ }
+ }
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return conflatedCallbackFlow {
+ val spec = getSpec()
+ if (spec != null) {
+ // If not added, we always try to add it
+ trySend(AutoAddSignal.Add(spec))
+ val listener =
+ SafetyController.Listener { isSafetyCenterEnabled ->
+ if (isSafetyCenterEnabled) {
+ trySend(AutoAddSignal.Add(spec))
+ } else {
+ trySend(AutoAddSignal.Remove(spec))
+ }
+ }
+
+ safetyController.addCallback(listener)
+
+ awaitClose { safetyController.removeCallback(listener) }
+ } else {
+ awaitClose {}
+ }
+ }
+ }
+
+ override val autoAddTracking: AutoAddTracking
+ get() = AutoAddTracking.Always
+
+ override val description = "SafetyCenterAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
new file mode 100644
index 0000000..b3bc25f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * [AutoAddable] for [QuickAccessWalletTile.TILE_SPEC].
+ *
+ * It will always try to add the tile if [WalletController.getWalletPosition] is non-null.
+ */
+@SysUISingleton
+class WalletAutoAddable
+@Inject
+constructor(
+ private val walletController: WalletController,
+) : AutoAddable {
+
+ private val spec = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return flow {
+ val position = walletController.getWalletPosition()
+ if (position != null) {
+ emit(AutoAddSignal.Add(spec, position))
+ }
+ }
+ }
+
+ override val autoAddTracking: AutoAddTracking
+ get() = AutoAddTracking.IfNotAdded(spec)
+
+ override val description = "WalletAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
new file mode 100644
index 0000000..5e3c348
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [WorkModeTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there is a managed profile for the current user, and a
+ * signal to remove it if there is not.
+ */
+@SysUISingleton
+class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable {
+
+ private val spec = TileSpec.create(WorkModeTile.TILE_SPEC)
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return conflatedCallbackFlow {
+ fun maybeSend(profiles: List<UserInfo>) {
+ if (profiles.any { it.id == userId }) {
+ // We are looking at the profiles of the correct user.
+ if (profiles.any { it.isManagedProfile }) {
+ trySend(AutoAddSignal.Add(spec))
+ } else {
+ trySend(AutoAddSignal.Remove(spec))
+ }
+ }
+ }
+
+ val callback =
+ object : UserTracker.Callback {
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ maybeSend(profiles)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ maybeSend(userTracker.userProfiles)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+ }
+
+ override val autoAddTracking = AutoAddTracking.Always
+
+ override val description = "WorkTileAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
new file mode 100644
index 0000000..b747393
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * Collects the signals coming from all registered [AutoAddable] and adds/removes tiles accordingly.
+ */
+@SysUISingleton
+class AutoAddInteractor
+@Inject
+constructor(
+ private val autoAddables: Set<@JvmSuppressWildcards AutoAddable>,
+ private val repository: AutoAddRepository,
+ private val dumpManager: DumpManager,
+ private val qsPipelineLogger: QSPipelineLogger,
+ @Application private val scope: CoroutineScope,
+) : Dumpable {
+
+ private val initialized = AtomicBoolean(false)
+
+ /** Start collection of signals following the user from [currentTilesInteractor]. */
+ fun init(currentTilesInteractor: CurrentTilesInteractor) {
+ if (!initialized.compareAndSet(false, true)) {
+ return
+ }
+
+ dumpManager.registerNormalDumpable(TAG, this)
+
+ scope.launch {
+ currentTilesInteractor.userId.collectLatest { userId ->
+ coroutineScope {
+ val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
+
+ autoAddables
+ .map { addable ->
+ val autoAddSignal = addable.autoAddSignal(userId)
+ when (val lifecycle = addable.autoAddTracking) {
+ is AutoAddTracking.Always -> autoAddSignal
+ is AutoAddTracking.Disabled -> emptyFlow()
+ is AutoAddTracking.IfNotAdded -> {
+ if (lifecycle.spec !in previouslyAdded.value) {
+ autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
+ } else {
+ emptyFlow()
+ }
+ }
+ }
+ }
+ .merge()
+ .collect { signal ->
+ when (signal) {
+ is AutoAddSignal.Add -> {
+ if (signal.spec !in previouslyAdded.value) {
+ currentTilesInteractor.addTile(signal.spec, signal.position)
+ qsPipelineLogger.logTileAutoAdded(
+ userId,
+ signal.spec,
+ signal.position
+ )
+ repository.markTileAdded(userId, signal.spec)
+ }
+ }
+ is AutoAddSignal.Remove -> {
+ currentTilesInteractor.removeTiles(setOf(signal.spec))
+ qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
+ repository.unmarkTileAdded(userId, signal.spec)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ with(pw.asIndenting()) {
+ println("AutoAddables:")
+ indentIfPossible { autoAddables.forEach { println(it.description) } }
+ }
+ }
+
+ companion object {
+ private const val TAG = "AutoAddInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
new file mode 100644
index 0000000..ed7b8bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Signal indicating when a tile needs to be auto-added or removed */
+sealed interface AutoAddSignal {
+ /** Tile for this object */
+ val spec: TileSpec
+
+ /** Signal for auto-adding a tile at [position]. */
+ data class Add(
+ override val spec: TileSpec,
+ val position: Int = POSITION_AT_END,
+ ) : AutoAddSignal
+
+ /** Signal for removing a tile. */
+ data class Remove(
+ override val spec: TileSpec,
+ ) : AutoAddSignal
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
new file mode 100644
index 0000000..154d045
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Strategy for when to track a particular [AutoAddable]. */
+sealed interface AutoAddTracking {
+
+ /**
+ * Indicates that the signals from the associated [AutoAddable] should all be collected and
+ * reacted accordingly. It may have [AutoAddSignal.Add] and [AutoAddSignal.Remove].
+ */
+ object Always : AutoAddTracking {
+ override fun toString(): String {
+ return "Always"
+ }
+ }
+
+ /**
+ * Indicates that the associated [AutoAddable] is [Disabled] and doesn't need to be collected.
+ */
+ object Disabled : AutoAddTracking {
+ override fun toString(): String {
+ return "Disabled"
+ }
+ }
+
+ /**
+ * Only the first [AutoAddSignal.Add] for each flow of signals needs to be collected, and only
+ * if the tile hasn't been auto-added yet. The associated [AutoAddable] will only emit
+ * [AutoAddSignal.Add].
+ */
+ data class IfNotAdded(val spec: TileSpec) : AutoAddTracking
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt
new file mode 100644
index 0000000..61fe5b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Tracks conditions for auto-adding or removing specific tiles.
+ *
+ * When creating a new [AutoAddable], it needs to be registered in a [Module] like
+ * [BaseAutoAddableModule], for example:
+ * ```
+ * @Binds
+ * @IntoSet
+ * fun providesMyAutoAddable(autoAddable: MyAutoAddable): AutoAddable
+ * ```
+ */
+interface AutoAddable {
+
+ /**
+ * Signals associated with a particular user indicating whether a particular tile needs to be
+ * auto-added or auto-removed.
+ */
+ fun autoAddSignal(userId: Int): Flow<AutoAddSignal>
+
+ /**
+ * Lifecycle for this object. It indicates in which cases [autoAddSignal] should be collected
+ */
+ val autoAddTracking: AutoAddTracking
+
+ /** Human readable description */
+ val description: String
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
new file mode 100644
index 0000000..224fc1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QSPipelineCoreStartable
+@Inject
+constructor(
+ private val currentTilesInteractor: CurrentTilesInteractor,
+ private val autoAddInteractor: AutoAddInteractor,
+ private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ override fun start() {
+ if (
+ featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) &&
+ featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+ ) {
+ autoAddInteractor.init(currentTilesInteractor)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
deleted file mode 100644
index bbd7234..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.pipeline.prototyping
-
-import android.util.Log
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
-import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.statusbar.commandline.Command
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.user.data.repository.UserRepository
-import java.io.PrintWriter
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.launch
-
-/**
- * Class for observing results while prototyping.
- *
- * The flows do their own logging, so we just need to make sure that they collect.
- *
- * This will be torn down together with the last of the new pipeline flags remaining here.
- */
-// TODO(b/270385608)
-@SysUISingleton
-class PrototypeCoreStartable
-@Inject
-constructor(
- private val tileSpecRepository: TileSpecRepository,
- private val autoAddRepository: AutoAddRepository,
- private val userRepository: UserRepository,
- private val featureFlags: FeatureFlags,
- @Application private val scope: CoroutineScope,
- private val commandRegistry: CommandRegistry,
-) : CoreStartable {
-
- @OptIn(ExperimentalCoroutinesApi::class)
- override fun start() {
- if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
- scope.launch {
- userRepository.selectedUserInfo
- .flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) }
- .collect {}
- }
- if (featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
- scope.launch {
- userRepository.selectedUserInfo
- .flatMapLatest { user -> autoAddRepository.autoAddedTiles(user.id) }
- .collect { tiles -> Log.d(TAG, "Auto-added tiles: $tiles") }
- }
- }
- commandRegistry.registerCommand(COMMAND, ::CommandExecutor)
- }
- }
-
- private inner class CommandExecutor : Command {
- override fun execute(pw: PrintWriter, args: List<String>) {
- if (args.size < 2) {
- pw.println("Error: needs at least two arguments")
- return
- }
- val spec = TileSpec.create(args[1])
- if (spec == TileSpec.Invalid) {
- pw.println("Error: Invalid tile spec ${args[1]}")
- }
- if (args[0] == "add") {
- performAdd(args, spec)
- pw.println("Requested tile added")
- } else if (args[0] == "remove") {
- performRemove(args, spec)
- pw.println("Requested tile removed")
- } else {
- pw.println("Error: unknown command")
- }
- }
-
- private fun performAdd(args: List<String>, spec: TileSpec) {
- val position = args.getOrNull(2)?.toInt() ?: TileSpecRepository.POSITION_AT_END
- val user = args.getOrNull(3)?.toInt() ?: userRepository.getSelectedUserInfo().id
- scope.launch { tileSpecRepository.addTile(user, spec, position) }
- }
-
- private fun performRemove(args: List<String>, spec: TileSpec) {
- val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
- scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
- }
-
- override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar $COMMAND:")
- pw.println(" add <spec> [position] [user]")
- pw.println(" remove <spec> [user]")
- }
- }
-
- companion object {
- private const val COMMAND = "qs-pipeline"
- private const val TAG = "PrototypeCoreStartable"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index af1cd09..11b5dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -52,7 +52,11 @@
internal constructor(
override val spec: String,
val componentName: ComponentName,
- ) : TileSpec(spec)
+ ) : TileSpec(spec) {
+ override fun toString(): String {
+ return "CustomTileSpec(${componentName.toShortString()})"
+ }
+ }
companion object {
/** Create a [TileSpec] from the string [spec]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 8318ec9..d400faa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -19,6 +19,7 @@
import android.annotation.UserIdInt
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
+import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog
import com.android.systemui.qs.pipeline.dagger.QSTileListLog
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -32,10 +33,12 @@
@Inject
constructor(
@QSTileListLog private val tileListLogBuffer: LogBuffer,
+ @QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer,
) {
companion object {
const val TILE_LIST_TAG = "QSTileListLog"
+ const val AUTO_ADD_TAG = "QSAutoAddableLog"
}
/**
@@ -136,6 +139,31 @@
)
}
+ fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) {
+ tileAutoAddLogBuffer.log(
+ AUTO_ADD_TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = userId
+ int2 = position
+ str1 = spec.toString()
+ },
+ { "Tile $str1 auto added for user $int1 at position $int2" }
+ )
+ }
+
+ fun logTileAutoRemoved(userId: Int, spec: TileSpec) {
+ tileAutoAddLogBuffer.log(
+ AUTO_ADD_TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = userId
+ str1 = spec.toString()
+ },
+ { "Tile $str1 auto removed for user $int1" }
+ )
+ }
+
/** Reasons for destroying an existing tile. */
enum class TileDestroyedReason(val readable: String) {
TILE_REMOVED("Tile removed from current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
index 59afb18..9702bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
@@ -18,19 +18,19 @@
import android.view.View;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
/**
* Calculates and moves the QS frame vertically.
*/
public abstract class QsFrameTranslateController {
- protected CentralSurfaces mCentralSurfaces;
+ protected DisplayMetricsRepository mDisplayMetricsRepository;
- public QsFrameTranslateController(CentralSurfaces centralSurfaces) {
- mCentralSurfaces = centralSurfaces;
+ public QsFrameTranslateController(DisplayMetricsRepository displayMetricsRepository) {
+ mDisplayMetricsRepository = displayMetricsRepository;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
index 85b522c..e429b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
@@ -19,9 +19,9 @@
import android.view.View;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import javax.inject.Inject;
@@ -34,8 +34,8 @@
public class QsFrameTranslateImpl extends QsFrameTranslateController {
@Inject
- public QsFrameTranslateImpl(CentralSurfaces centralSurfaces) {
- super(centralSurfaces);
+ public QsFrameTranslateImpl(DisplayMetricsRepository displayMetricsRepository) {
+ super(displayMetricsRepository);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index f6d53b3..5eafa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,6 +20,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
@@ -28,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.dagger.NightDisplayListenerModule;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.AutoAddTracker;
@@ -82,7 +84,8 @@
private final HotspotController mHotspotController;
private final DataSaverController mDataSaverController;
private final ManagedProfileController mManagedProfileController;
- private final NightDisplayListener mNightDisplayListener;
+ private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
+ private NightDisplayListener mNightDisplayListener;
private final CastController mCastController;
private final DeviceControlsController mDeviceControlsController;
private final WalletController mWalletController;
@@ -98,7 +101,7 @@
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
- NightDisplayListener nightDisplayListener,
+ NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
@@ -114,7 +117,7 @@
mHotspotController = hotspotController;
mDataSaverController = dataSaverController;
mManagedProfileController = managedProfileController;
- mNightDisplayListener = nightDisplayListener;
+ mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
mCastController = castController;
mReduceBrightColorsController = reduceBrightColorsController;
mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
@@ -157,6 +160,10 @@
mDataSaverController.addCallback(mDataSaverListener);
}
mManagedProfileController.addCallback(mProfileCallback);
+
+ mNightDisplayListener = mNightDisplayListenerBuilder
+ .setUser(mCurrentUser.getIdentifier())
+ .build();
if (!mAutoTracker.isAdded(NIGHT)
&& ColorDisplayManager.isNightDisplayAvailable(mContext)) {
mNightDisplayListener.setCallback(mNightDisplayCallback);
@@ -193,7 +200,8 @@
mHotspotController.removeCallback(mHotspotCallback);
mDataSaverController.removeCallback(mDataSaverListener);
mManagedProfileController.removeCallback(mProfileCallback);
- if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+ if (ColorDisplayManager.isNightDisplayAvailable(mContext)
+ && mNightDisplayListener != null) {
mNightDisplayListener.setCallback(null);
}
if (mIsReduceBrightColorsAvailable) {
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 0242e91..4ba09e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -40,6 +40,7 @@
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.qs.QSPanelController;
@@ -250,8 +251,12 @@
@Override
void dump(PrintWriter pwOriginal, String[] args);
+ /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+ @Deprecated
float getDisplayWidth();
+ /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+ @Deprecated
float getDisplayHeight();
void readyForKeyguardDone();
@@ -394,6 +399,9 @@
void setLaunchEmergencyActionOnFinishedWaking(boolean launch);
QSPanelController getQSPanelController();
+
+ /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+ @Deprecated
float getDisplayDensity();
void extendDozePulse();
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 4ae4c52..88ccae6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2114,16 +2114,19 @@
}
@Override
+ @Deprecated
public float getDisplayDensity() {
return mDisplayMetrics.density;
}
@Override
+ @Deprecated
public float getDisplayWidth() {
return mDisplayMetrics.widthPixels;
}
@Override
+ @Deprecated
public float getDisplayHeight() {
return mDisplayMetrics.heightPixels;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index e6b76ad..3b5aaea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -48,6 +48,7 @@
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyType;
@@ -74,6 +75,7 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.DateFormatUtil;
import java.io.PrintWriter;
@@ -121,9 +123,12 @@
private final String mSlotCamera;
private final String mSlotSensorsOff;
private final String mSlotScreenRecord;
+ private final String mSlotConnectedDisplay;
private final int mDisplayId;
private final SharedPreferences mSharedPreferences;
private final DateFormatUtil mDateFormatUtil;
+ private final JavaAdapter mJavaAdapter;
+ private final ConnectedDisplayInteractor mConnectedDisplayInteractor;
private final TelecomManager mTelecomManager;
private final Handler mHandler;
@@ -182,9 +187,13 @@
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker,
PrivacyItemController privacyItemController,
- PrivacyLogger privacyLogger) {
+ PrivacyLogger privacyLogger,
+ ConnectedDisplayInteractor connectedDisplayInteractor,
+ JavaAdapter javaAdapter
+ ) {
mIconController = iconController;
mCommandQueue = commandQueue;
+ mConnectedDisplayInteractor = connectedDisplayInteractor;
mBroadcastDispatcher = broadcastDispatcher;
mHandler = new Handler(looper);
mResources = resources;
@@ -211,8 +220,11 @@
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
mPrivacyLogger = privacyLogger;
+ mJavaAdapter = javaAdapter;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
+ mSlotConnectedDisplay = resources.getString(
+ com.android.internal.R.string.status_bar_connected_display);
mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
mSlotBluetooth = resources.getString(com.android.internal.R.string.status_bar_bluetooth);
mSlotTty = resources.getString(com.android.internal.R.string.status_bar_tty);
@@ -285,6 +297,10 @@
mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
mIconController.setIconVisibility(mSlotCast, false);
+ // connected display
+ mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+ mIconController.setIconVisibility(mSlotConnectedDisplay, false);
+
// hotspot
mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
mResources.getString(R.string.accessibility_status_bar_hotspot));
@@ -342,6 +358,8 @@
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
mRecordingController.addCallback(this);
+ mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(),
+ this::onConnectedDisplayAvailabilityChanged);
mCommandQueue.addCallback(this);
}
@@ -800,4 +818,14 @@
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
}
+
+ private void onConnectedDisplayAvailabilityChanged(ConnectedDisplayInteractor.State state) {
+ boolean visible = state != ConnectedDisplayInteractor.State.DISCONNECTED;
+
+ if (DEBUG) {
+ Log.d(TAG, "connected_display: " + (visible ? "showing" : "hiding") + " icon");
+ }
+
+ mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e63875b..cb2a78d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -703,7 +703,7 @@
@Override
public void reset(boolean hideBouncerWhenShowing) {
- if (mKeyguardStateController.isShowing()) {
+ if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
final boolean isOccluded = mKeyguardStateController.isOccluded();
// Hide quick settings.
mShadeViewController.resetViews(/* animate= */ !isOccluded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index fcae23b..0d58079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -14,9 +14,6 @@
package com.android.systemui.statusbar.phone.fragment;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
-
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Fragment;
@@ -39,6 +36,7 @@
import androidx.core.animation.Animator;
import com.android.app.animation.Interpolators;
+import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -77,6 +75,8 @@
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -99,12 +99,16 @@
private static final String EXTRA_PANEL_STATE = "panel_state";
public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager";
public static final int FADE_IN_DURATION = 320;
+ public static final int FADE_OUT_DURATION = 160;
public static final int FADE_IN_DELAY = 50;
+ private static final int SOURCE_SYSTEM_EVENT_ANIMATOR = 1;
+ private static final int SOURCE_OTHER = 2;
private StatusBarFragmentComponent mStatusBarFragmentComponent;
private PhoneStatusBarView mStatusBar;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
private final ShadeViewController mShadeViewController;
+ private MultiSourceMinAlphaController mEndSideAlphaController;
private LinearLayout mEndSideContent;
private View mClockView;
private View mOngoingCallChip;
@@ -149,7 +153,7 @@
}
};
private OperatorNameViewController mOperatorNameViewController;
- private StatusBarSystemEventAnimator mSystemEventAnimator;
+ private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
private final CarrierConfigChangedListener mCarrierConfigCallback =
new CarrierConfigChangedListener() {
@@ -297,14 +301,14 @@
updateBlockedIcons();
mStatusBarIconController.addIconGroup(mDarkIconManager);
mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
+ mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
mClockView = mStatusBar.findViewById(R.id.clock);
mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
showEndSideContent(false);
showClock(false);
initOperatorName();
initNotificationIconArea();
- mSystemEventAnimator =
- new StatusBarSystemEventAnimator(mEndSideContent, getResources());
+ mSystemEventAnimator = getSystemEventAnimator();
mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
@@ -593,18 +597,27 @@
}
private void hideEndSideContent(boolean animate) {
- animateHide(mEndSideContent, animate);
+ if (!animate) {
+ mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
+ } else {
+ mEndSideAlphaController.animateToAlpha(/*alpha*/ 0f, SOURCE_OTHER, FADE_OUT_DURATION,
+ InterpolatorsAndroidX.ALPHA_OUT, /*startDelay*/ 0);
+ }
}
private void showEndSideContent(boolean animate) {
- // Only show the system icon area if we are not currently animating
- int state = mAnimationScheduler.getAnimationState();
- if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
- animateShow(mEndSideContent, animate);
+ if (!animate) {
+ mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
+ return;
+ }
+ if (mKeyguardStateController.isKeyguardFadingAway()) {
+ mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER,
+ mKeyguardStateController.getKeyguardFadingAwayDuration(),
+ InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN,
+ mKeyguardStateController.getKeyguardFadingAwayDelay());
} else {
- // We are in the middle of a system status event animation, which will animate the
- // alpha (but not the visibility). Allow the view to become visible again
- mEndSideContent.setVisibility(View.VISIBLE);
+ mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, FADE_IN_DURATION,
+ InterpolatorsAndroidX.ALPHA_IN, FADE_IN_DELAY);
}
}
@@ -671,7 +684,7 @@
v.animate()
.alpha(0f)
- .setDuration(160)
+ .setDuration(FADE_OUT_DURATION)
.setStartDelay(0)
.setInterpolator(Interpolators.ALPHA_OUT)
.withEndAction(() -> v.setVisibility(state));
@@ -754,6 +767,16 @@
return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
}
+ private StatusBarSystemEventDefaultAnimator getSystemEventAnimator() {
+ return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
+ mEndSideAlphaController.setAlpha(alpha, SOURCE_SYSTEM_EVENT_ANIMATOR);
+ return Unit.INSTANCE;
+ }, (translationX) -> {
+ mEndSideContent.setTranslationX(translationX);
+ return Unit.INSTANCE;
+ }, /*isAnimationRunning*/ false);
+ }
+
private void updateStatusBarLocation(int left, int right) {
int leftMargin = left - mStatusBar.getLeft();
int rightMargin = mStatusBar.getRight() - right;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
new file mode 100644
index 0000000..c8836e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+import android.view.View
+import androidx.core.animation.Interpolator
+import androidx.core.animation.ValueAnimator
+import com.android.app.animation.InterpolatorsAndroidX
+
+/**
+ * A controller that keeps track of multiple sources applying alpha value changes to a view. It will
+ * always apply the minimum alpha value of all sources.
+ */
+internal class MultiSourceMinAlphaController
+@JvmOverloads
+constructor(private val view: View, private val initialAlpha: Float = 1f) {
+
+ private val alphas = mutableMapOf<Int, Float>()
+ private val animators = mutableMapOf<Int, ValueAnimator>()
+
+ /**
+ * Sets the alpha of the provided source and applies it to the view (if no other source has set
+ * a lower alpha currently). If an animator of the same source is still running (i.e.
+ * [animateToAlpha] was called before), that animator is cancelled.
+ */
+ fun setAlpha(alpha: Float, sourceId: Int) {
+ animators[sourceId]?.cancel()
+ updateAlpha(alpha, sourceId)
+ }
+
+ /** Animates to the alpha of the provided source. */
+ fun animateToAlpha(
+ alpha: Float,
+ sourceId: Int,
+ duration: Long,
+ interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN,
+ startDelay: Long = 0
+ ) {
+ animators[sourceId]?.cancel()
+ val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha)
+ animator.duration = duration
+ animator.startDelay = startDelay
+ animator.interpolator = interpolator
+ animator.addUpdateListener { updateAlpha(animator.animatedValue as Float, sourceId) }
+ animator.start()
+ animators[sourceId] = animator
+ }
+
+ fun reset() {
+ alphas.clear()
+ animators.forEach { it.value.cancel() }
+ animators.clear()
+ applyAlphaToView()
+ }
+
+ private fun updateAlpha(alpha: Float, sourceId: Int) {
+ alphas[sourceId] = alpha
+ applyAlphaToView()
+ }
+
+ private fun applyAlphaToView() {
+ val minAlpha = getMinAlpha()
+ view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
+ view.alpha = minAlpha
+ }
+
+ private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d3b4190..5a56baf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -97,21 +97,7 @@
`when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
.thenReturn(deleteButton)
`when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
- pinViewController =
- KeyguardPinViewController(
- keyguardPinView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- mKeyguardSecurityCallback,
- keyguardMessageAreaControllerFactory,
- mLatencyTracker,
- liftToActivateListener,
- mEmergencyButtonController,
- falsingCollector,
- postureController,
- featureFlags
- )
+ constructViewController()
}
@Test
@@ -135,8 +121,10 @@
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
+ constructViewController()
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -150,8 +138,10 @@
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
`when`(passwordTextView.text).thenReturn("")
+ constructViewController()
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -163,4 +153,22 @@
pinViewController.handleAttemptLockout(0)
verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
+
+ fun constructViewController() {
+ pinViewController =
+ KeyguardPinViewController(
+ keyguardPinView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ mKeyguardSecurityCallback,
+ keyguardMessageAreaControllerFactory,
+ mLatencyTracker,
+ liftToActivateListener,
+ mEmergencyButtonController,
+ falsingCollector,
+ postureController,
+ featureFlags
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index c783325..ea3289c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -66,30 +66,6 @@
}
@Test
- fun unlockDevice() =
- testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- assertThat(isUnlocked).isFalse()
-
- underTest.unlockDevice()
- runCurrent()
-
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
- fun biometricUnlock() =
- testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- assertThat(isUnlocked).isFalse()
-
- underTest.biometricUnlock()
- runCurrent()
-
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
fun toggleBypassEnabled() =
testScope.runTest {
val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
@@ -105,7 +81,7 @@
@Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
- underTest.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
@@ -117,7 +93,7 @@
@Test
fun isAuthenticationRequired_lockedAndNotSecured_false() =
testScope.runTest {
- underTest.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
@@ -127,7 +103,7 @@
@Test
fun isAuthenticationRequired_unlockedAndSecured_false() =
testScope.runTest {
- underTest.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
@@ -139,7 +115,7 @@
@Test
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
testScope.runTest {
- underTest.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
@@ -147,67 +123,55 @@
}
@Test
- fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
+ fun authenticate_withCorrectPin_returnsTrue() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
- assertThat(isUnlocked).isTrue()
assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
- fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
+ fun authenticate_withIncorrectPin_returnsFalse() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
- fun authenticate_withEmptyPin_returnsFalseAndDoesNotUnlockDevice() =
+ fun authenticate_withEmptyPin_returnsFalse() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf())).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
- fun authenticate_withCorrectMaxLengthPin_returnsTrueAndUnlocksDevice() =
+ fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(9999999999999999)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(List(16) { 9 })).isTrue()
- assertThat(isUnlocked).isTrue()
assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
- fun authenticate_withCorrectTooLongPin_returnsFalseAndDoesNotUnlockDevice() =
+ fun authenticate_withCorrectTooLongPin_returnsFalse() =
testScope.runTest {
// Max pin length is 16 digits. To avoid issues with overflows, this test ensures
// that all pins > 16 decimal digits are rejected.
@@ -216,52 +180,42 @@
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(99999999999999999)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
- fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
+ fun authenticate_withCorrectPassword_returnsTrue() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate("password".toList())).isTrue()
- assertThat(isUnlocked).isTrue()
assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
- fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
+ fun authenticate_withIncorrectPassword_returnsFalse() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate("alohomora".toList())).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
- fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
+ fun authenticate_withCorrectPattern_returnsTrue() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(
listOf(
@@ -280,7 +234,6 @@
)
)
)
- assertThat(isUnlocked).isFalse()
assertThat(
underTest.authenticate(
@@ -301,15 +254,13 @@
)
)
.isTrue()
- assertThat(isUnlocked).isTrue()
assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
- fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
+ fun authenticate_withIncorrectPattern_returnsFalse() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(
listOf(
@@ -328,7 +279,6 @@
)
)
)
- assertThat(isUnlocked).isFalse()
assertThat(
underTest.authenticate(
@@ -349,22 +299,18 @@
)
)
.isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
- fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNullAndHasNoEffect() =
+ fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNull() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(), tryAutoConfirm = true)).isNull()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(0)
}
@@ -372,14 +318,11 @@
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(1, 2, 3), tryAutoConfirm = true)).isNull()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(0)
}
@@ -387,14 +330,11 @@
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@@ -402,15 +342,12 @@
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(1, 2, 3, 4, 5), tryAutoConfirm = true))
.isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@@ -418,14 +355,11 @@
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(1)
}
@@ -433,14 +367,11 @@
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = false)
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(0)
}
@@ -448,14 +379,11 @@
fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
testScope.runTest {
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
- assertThat(isUnlocked).isFalse()
assertThat(failedAttemptCount).isEqualTo(0)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 821c2cb..30e5447 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1316,12 +1316,22 @@
// WHEN ACTION_DOWN is received
when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
processorResultDown);
- MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
- mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricExecutor.runAllReady();
- downEvent.recycle();
- // THEN the touch is pilfered
+ // WHEN ACTION_MOVE is received after
+ final TouchProcessorResult processorResultUnchanged =
+ new TouchProcessorResult.ProcessedTouch(
+ InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData);
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultUnchanged);
+ event.setAction(ACTION_MOVE);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+ mBiometricExecutor.runAllReady();
+ event.recycle();
+
+ // THEN only pilfer once on the initial down
verify(mInputManager).pilferPointers(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 92cf0a5..d09353b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -71,7 +71,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
underTest.showOrUnlockDevice("container1")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -104,7 +104,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
underTest.showOrUnlockDevice("container1")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -134,7 +134,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = false)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
underTest.showOrUnlockDevice("container1")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.clearMessage()
@@ -158,7 +158,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
underTest.showOrUnlockDevice("container1")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -190,7 +190,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(emptyList())
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
underTest.showOrUnlockDevice("container1")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -226,7 +226,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
underTest.showOrUnlockDevice("container1")
@@ -239,7 +239,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
underTest.showOrUnlockDevice("container1")
@@ -254,7 +254,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
val customMessage = "Hello there!"
underTest.showOrUnlockDevice("container1", customMessage)
@@ -268,13 +268,17 @@
testScope.runTest {
val throttling by collectLastValue(underTest.throttling)
val message by collectLastValue(underTest.message)
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
+ runCurrent()
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ runCurrent()
+ assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
assertThat(throttling).isNull()
- assertThat(message).isEqualTo("")
- assertThat(isUnlocked).isFalse()
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
// Wrong PIN.
assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
@@ -285,9 +289,9 @@
assertThat(throttling).isNotNull()
assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
- // Correct PIN, but throttled, so doesn't unlock:
+ // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
- assertThat(isUnlocked).isFalse()
+ assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
throttling?.totalDurationSec?.let { seconds ->
@@ -301,11 +305,28 @@
}
assertThat(message).isEqualTo("")
assertThat(throttling).isNull()
- assertThat(isUnlocked).isFalse()
+ assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
- // Correct PIN and no longer throttled so unlocks:
+ // Correct PIN and no longer throttled so changes to the Gone scene:
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
- assertThat(isUnlocked).isTrue()
+ assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun switchesToGone_whenUnlocked() =
+ testScope.runTest {
+ utils.authenticationRepository.setUnlocked(false)
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ utils.authenticationRepository.setUnlocked(true)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
private fun assertTryAgainMessage(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 53f972e..5ffc471 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -23,10 +23,14 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,16 +57,26 @@
@Test
fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
testScope.runTest {
+ var authMethodViewModel: AuthMethodBouncerViewModel? = null
+
authMethodsToTest().forEach { authMethod ->
utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ val job = underTest.authMethod.onEach { authMethodViewModel = it }.launchIn(this)
+ runCurrent()
- val authMethodViewModel: AuthMethodBouncerViewModel? by
- collectLastValue(underTest.authMethod)
if (authMethod.isSecure) {
- assertThat(authMethodViewModel).isNotNull()
+ assertWithMessage("View-model unexpectedly null for auth method $authMethod")
+ .that(authMethodViewModel)
+ .isNotNull()
} else {
- assertThat(authMethodViewModel).isNull()
+ assertWithMessage(
+ "View-model unexpectedly non-null for auth method $authMethod"
+ )
+ .that(authMethodViewModel)
+ .isNull()
}
+
+ job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 7d65c80..699571b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -72,39 +72,34 @@
@Test
fun onShown() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
assertThat(password).isEqualTo("")
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onPasswordInputChanged() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -113,44 +108,38 @@
assertThat(message?.text).isEmpty()
assertThat(password).isEqualTo("password")
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onAuthenticateKeyPressed_whenCorrect() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("password")
underTest.onAuthenticateKeyPressed()
- assertThat(isUnlocked).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun onAuthenticateKeyPressed_whenWrong() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -159,30 +148,26 @@
assertThat(password).isEqualTo("")
assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onAuthenticateKeyPressed_correctAfterWrong() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
underTest.onAuthenticateKeyPressed()
assertThat(password).isEqualTo("")
assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Enter the correct password:
@@ -191,7 +176,6 @@
underTest.onAuthenticateKeyPressed()
- assertThat(isUnlocked).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 9f16358..9a1f584 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -74,7 +74,6 @@
@Test
fun onShown() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
@@ -82,9 +81,8 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -92,14 +90,12 @@
assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onDragStart() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
@@ -107,9 +103,8 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -119,23 +114,20 @@
assertThat(message?.text).isEmpty()
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onDragEnd_whenCorrect() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -169,14 +161,12 @@
underTest.onDragEnd()
- assertThat(isUnlocked).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun onDragEnd_whenWrong() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
@@ -184,9 +174,8 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -204,14 +193,12 @@
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
assertThat(message?.text).isEqualTo(WRONG_PATTERN)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onDragEnd_correctAfterWrong() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
@@ -219,9 +206,8 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -237,7 +223,6 @@
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
assertThat(message?.text).isEqualTo(WRONG_PATTERN)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Enter the correct pattern:
@@ -252,7 +237,6 @@
underTest.onDragEnd()
- assertThat(isUnlocked).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index a9907c0..61432e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -81,36 +81,31 @@
@Test
fun onShown() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
assertThat(entries).hasSize(0)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onPinButtonClicked() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -120,23 +115,20 @@
assertThat(message?.text).isEmpty()
assertThat(entries).hasSize(1)
assertThat(entries?.map { it.input }).containsExactly(1)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onBackspaceButtonClicked() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -147,23 +139,19 @@
assertThat(message?.text).isEmpty()
assertThat(entries).hasSize(0)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onPinEdit() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -183,16 +171,14 @@
@Test
fun onBackspaceButtonLongPressed() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -205,21 +191,18 @@
assertThat(message?.text).isEmpty()
assertThat(entries).hasSize(0)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onAuthenticateButtonClicked_whenCorrect() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -229,23 +212,20 @@
underTest.onAuthenticateButtonClicked()
- assertThat(isUnlocked).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun onAuthenticateButtonClicked_whenWrong() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -258,23 +238,20 @@
assertThat(entries).hasSize(0)
assertThat(message?.text).isEqualTo(WRONG_PIN)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onAuthenticateButtonClicked_correctAfterWrong() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -285,7 +262,6 @@
underTest.onAuthenticateButtonClicked()
assertThat(message?.text).isEqualTo(WRONG_PIN)
assertThat(entries).hasSize(0)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Enter the correct PIN:
@@ -297,21 +273,18 @@
underTest.onAuthenticateButtonClicked()
- assertThat(isUnlocked).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -319,23 +292,20 @@
underTest.onPinButtonClicked(3)
underTest.onPinButtonClicked(4)
- assertThat(isUnlocked).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun onAutoConfirm_whenWrong() =
testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -345,7 +315,6 @@
assertThat(entries).hasSize(0)
assertThat(message?.text).isEqualTo(WRONG_PIN)
- assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
new file mode 100644
index 0000000..dd741b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisplayMetricsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: DisplayMetricsRepository
+
+ private val testScope = TestScope(StandardTestDispatcher())
+ private val configurationController = FakeConfigurationController()
+
+ private val displayMetrics =
+ DisplayMetrics().apply { this.heightPixels = INITIAL_HEIGHT_PIXELS }
+ private val mockContext: Context = mock()
+ private val mockDisplay: Display = mock()
+
+ @Before
+ fun setUp() {
+ underTest =
+ DisplayMetricsRepository(
+ testScope.backgroundScope,
+ configurationController,
+ displayMetrics,
+ mockContext,
+ LogBuffer("TestBuffer", maxSize = 10, logcatEchoTracker = mock())
+ )
+ whenever(mockContext.display).thenReturn(mockDisplay)
+ }
+
+ @Test
+ fun heightPixels_getsInitialValue() {
+ assertThat(underTest.heightPixels).isEqualTo(INITIAL_HEIGHT_PIXELS)
+ }
+
+ @Test
+ fun heightPixels_configChanged_heightUpdated() =
+ testScope.runTest {
+ runCurrent()
+
+ updateDisplayMetrics(456)
+ configurationController.notifyConfigurationChanged()
+ runCurrent()
+
+ assertThat(underTest.heightPixels).isEqualTo(456)
+
+ updateDisplayMetrics(23)
+ configurationController.notifyConfigurationChanged()
+ runCurrent()
+
+ assertThat(underTest.heightPixels).isEqualTo(23)
+ }
+
+ private fun updateDisplayMetrics(newHeight: Int) {
+ whenever(mockDisplay.getMetrics(displayMetrics)).thenAnswer {
+ it.getArgument<DisplayMetrics>(0).heightPixels = newHeight
+ Unit
+ }
+ }
+
+ private companion object {
+ const val INITIAL_HEIGHT_PIXELS = 345
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
new file mode 100644
index 0000000..1b597f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class ConnectedDisplayInteractorTest : SysuiTestCase() {
+
+ private val fakeDisplayRepository = FakeDisplayRepository()
+ private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
+ ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
+ @Test
+ fun displayState_nullDisplays_disconnected() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(emptySet())
+
+ assertThat(value).isEqualTo(State.DISCONNECTED)
+ }
+
+ @Test
+ fun displayState_emptyDisplays_disconnected() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(emptySet())
+
+ assertThat(value).isEqualTo(State.DISCONNECTED)
+ }
+
+ @Test
+ fun displayState_internalDisplay_disconnected() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(setOf(display(type = TYPE_INTERNAL)))
+
+ assertThat(value).isEqualTo(State.DISCONNECTED)
+ }
+
+ @Test
+ fun displayState_externalDisplay_connected() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(setOf(display(type = TYPE_EXTERNAL)))
+
+ assertThat(value).isEqualTo(State.CONNECTED)
+ }
+
+ @Test
+ fun displayState_multipleExternalDisplays_connected() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(
+ setOf(display(type = TYPE_EXTERNAL), display(type = TYPE_EXTERNAL))
+ )
+
+ assertThat(value).isEqualTo(State.CONNECTED)
+ }
+
+ @Test
+ fun displayState_externalSecure_connectedSecure() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(
+ setOf(display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE))
+ )
+
+ assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+ }
+
+ @Test
+ fun displayState_multipleExternal_onlyOneSecure_connectedSecure() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(
+ setOf(
+ display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE),
+ display(type = TYPE_EXTERNAL, flags = 0)
+ )
+ )
+
+ assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+ }
+
+ private fun TestScope.lastValue(): FlowValue<State?> =
+ collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+ private fun display(type: Int, flags: Int = 0): Display {
+ return mock<Display>().also { mockDisplay ->
+ whenever(mockDisplay.type).thenReturn(type)
+ whenever(mockDisplay.flags).thenReturn(flags)
+ }
+ }
+
+ private class FakeDisplayRepository : DisplayRepository {
+ private val flow = MutableSharedFlow<Set<Display>>()
+ suspend fun emit(value: Set<Display>) = flow.emit(value)
+ override val displays: Flow<Set<Display>>
+ get() = flow
+ }
+}
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 12a9f94..6f7c217 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -780,6 +780,11 @@
assertTrue(mViewMediator.isShowingAndNotOccluded());
}
+ @Test
+ public void testBouncerSwipeDown() {
+ mViewMediator.getViewMediatorCallback().onBouncerSwipeDown();
+ verify(mStatusBarKeyguardViewManager).reset(true);
+ }
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 953d618..25573de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -235,11 +235,10 @@
fun isKeyguardUnlocked() =
testScope.runTest {
whenever(keyguardStateController.isUnlocked).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
+ val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
runCurrent()
- assertThat(latest).isFalse()
+ assertThat(isKeyguardUnlocked).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
@@ -247,14 +246,12 @@
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
runCurrent()
- assertThat(latest).isTrue()
+ assertThat(isKeyguardUnlocked).isTrue()
whenever(keyguardStateController.isUnlocked).thenReturn(false)
- captor.value.onUnlockedChanged()
+ captor.value.onKeyguardShowingChanged()
runCurrent()
- assertThat(latest).isFalse()
-
- job.cancel()
+ assertThat(isKeyguardUnlocked).isFalse()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index c9fce94..abbdc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -60,10 +60,10 @@
testScope.runTest {
val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
assertThat(isDeviceLocked).isTrue()
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
assertThat(isDeviceLocked).isFalse()
}
@@ -72,7 +72,7 @@
testScope.runTest {
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(isSwipeToDismissEnabled).isTrue()
@@ -83,7 +83,7 @@
testScope.runTest {
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(isSwipeToDismissEnabled).isFalse()
@@ -93,7 +93,7 @@
fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
@@ -108,7 +108,7 @@
fun dismissLockScreen_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
@@ -123,7 +123,7 @@
fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -139,61 +139,16 @@
runCurrent()
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
runCurrent()
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
}
@Test
- fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- authenticationInteractor.lockDevice()
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
- if (!authenticationInteractor.isBypassEnabled.value) {
- authenticationInteractor.toggleBypassEnabled()
- }
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
- authenticationInteractor.biometricUnlock()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
-
- @Test
- fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- authenticationInteractor.lockDevice()
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
- if (authenticationInteractor.isBypassEnabled.value) {
- authenticationInteractor.toggleBypassEnabled()
- }
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
- authenticationInteractor.biometricUnlock()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- }
-
- @Test
- fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
- testScope.runTest {
- val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- assertThat(isUnlocked).isFalse()
-
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
testScope.runTest {
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index e8c01f0..ff4ec4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -75,7 +75,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
assertThat((lockButtonIcon as? Icon.Resource)?.res)
.isEqualTo(R.drawable.ic_device_lock_on)
@@ -88,7 +88,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
assertThat((lockButtonIcon as? Icon.Resource)?.res)
.isEqualTo(R.drawable.ic_device_lock_off)
@@ -99,7 +99,7 @@
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -111,7 +111,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
}
@@ -123,7 +123,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
runCurrent()
underTest.onLockButtonClicked()
@@ -138,7 +138,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -153,7 +153,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
@@ -168,7 +168,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
underTest.onLockButtonClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index d98bcee..9781baa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -107,7 +108,7 @@
@Mock
private TunerService mTunerService;
@Mock
- private Provider<AutoTileManager> mAutoTiles;
+ private AutoTileManager mAutoTiles;
@Mock
private ShadeController mShadeController;
@Mock
@@ -140,6 +141,7 @@
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
+ mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
mMainExecutor = new FakeExecutor(new FakeSystemClock());
@@ -160,9 +162,10 @@
mSecureSettings = new FakeSettings();
saveSetting("");
mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
- mPluginManager, mTunerService, mAutoTiles, mShadeController,
+ mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
+ mMainExecutor.runAllReady();
mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@Override
@@ -296,11 +299,20 @@
StringWriter w = new StringWriter();
PrintWriter pw = new PrintWriter(w);
mQSTileHost.dump(pw, new String[]{});
- String output = "QSTileHost:\n"
- + TestTile1.class.getSimpleName() + ":\n"
- + " " + MOCK_STATE_STRING + "\n"
- + TestTile2.class.getSimpleName() + ":\n"
- + " " + MOCK_STATE_STRING + "\n";
+
+ String output = "QSTileHost:" + "\n"
+ + "tile specs: [spec1, spec2]" + "\n"
+ + "current user: 0" + "\n"
+ + "is dirty: false" + "\n"
+ + "tiles:" + "\n"
+ + "TestTile1:" + "\n"
+ + " MockState" + "\n"
+ + "TestTile2:" + "\n"
+ + " MockState" + "\n";
+
+ System.out.println(output);
+ System.out.println(w.getBuffer().toString());
+
assertEquals(output, w.getBuffer().toString());
}
@@ -672,6 +684,17 @@
assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
}
+ @Test
+ public void testUserChange_flagOn_autoTileManagerNotified() {
+ mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true);
+ int currentUser = mUserTracker.getUserId();
+ clearInvocations(mAutoTiles);
+ when(mUserTracker.getUserId()).thenReturn(currentUser + 1);
+
+ mQSTileHost.onTuningChanged(SETTING, "a,b");
+ verify(mAutoTiles).changeUser(UserHandle.of(currentUser + 1));
+ }
+
private SharedPreferences getSharedPreferencesForUser(int user) {
return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
new file mode 100644
index 0000000..817ac61
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingListTest : SysuiTestCase() {
+
+ private val factory =
+ object : AutoAddableSetting.Factory {
+ override fun create(setting: String, spec: TileSpec): AutoAddableSetting {
+ return AutoAddableSetting(
+ mock(),
+ mock(),
+ setting,
+ spec,
+ )
+ }
+ }
+
+ @Test
+ fun correctLines_correctAutoAddables() {
+ val setting1 = "setting1"
+ val setting2 = "setting2"
+ val spec1 = TileSpec.create("spec1")
+ val spec2 = TileSpec.create(ComponentName("pkg", "cls"))
+
+ context.orCreateTestableResources.addOverride(
+ R.array.config_quickSettingsAutoAdd,
+ arrayOf(toStringLine(setting1, spec1), toStringLine(setting2, spec2))
+ )
+
+ val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+ assertThat(autoAddables)
+ .containsExactly(factory.create(setting1, spec1), factory.create(setting2, spec2))
+ }
+
+ @Test
+ fun malformedLine_ignored() {
+ val setting = "setting"
+ val spec = TileSpec.create("spec")
+
+ context.orCreateTestableResources.addOverride(
+ R.array.config_quickSettingsAutoAdd,
+ arrayOf(toStringLine(setting, spec), "bad_line")
+ )
+
+ val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+ assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+ }
+
+ @Test
+ fun invalidSpec_ignored() {
+ val setting = "setting"
+ val spec = TileSpec.create("spec")
+
+ context.orCreateTestableResources.addOverride(
+ R.array.config_quickSettingsAutoAdd,
+ arrayOf(toStringLine(setting, spec), "invalid:")
+ )
+
+ val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+ assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+ }
+
+ companion object {
+ private fun toStringLine(setting: String, spec: TileSpec) =
+ "$setting$SETTINGS_SEPARATOR${spec.spec}"
+ private const val SETTINGS_SEPARATOR = ":"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
new file mode 100644
index 0000000..36c3c9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingTest : SysuiTestCase() {
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val secureSettings = FakeSettings()
+ private val underTest =
+ AutoAddableSetting(
+ secureSettings,
+ testDispatcher,
+ SETTING,
+ SPEC,
+ )
+
+ @Test
+ fun settingNotSet_noSignal() =
+ testScope.runTest {
+ val userId = 0
+ val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+ assertThat(signal).isNull() // null means no emitted value
+ }
+
+ @Test
+ fun settingSetTo0_noSignal() =
+ testScope.runTest {
+ val userId = 0
+ val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+ secureSettings.putIntForUser(SETTING, 0, userId)
+
+ assertThat(signal).isNull() // null means no emitted value
+ }
+
+ @Test
+ fun settingSetToNon0_signal() =
+ testScope.runTest {
+ val userId = 0
+ val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+ secureSettings.putIntForUser(SETTING, 42, userId)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun settingSetForUser_onlySignalInThatUser() =
+ testScope.runTest {
+ val signal0 by collectLastValue(underTest.autoAddSignal(0))
+ val signal1 by collectLastValue(underTest.autoAddSignal(1))
+
+ secureSettings.putIntForUser(SETTING, /* value */ 42, /* userHandle */ 1)
+
+ assertThat(signal0).isNull()
+ assertThat(signal1).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun multipleNonZeroChanges_onlyOneSignal() =
+ testScope.runTest {
+ val userId = 0
+ val signals by collectValues(underTest.autoAddSignal(userId))
+
+ secureSettings.putIntForUser(SETTING, 1, userId)
+ secureSettings.putIntForUser(SETTING, 2, userId)
+
+ assertThat(signals.size).isEqualTo(1)
+ }
+
+ @Test
+ fun strategyIfNotAdded() {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+ }
+
+ companion object {
+ private const val SETTING = "setting"
+ private val SPEC = TileSpec.create("spec")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
new file mode 100644
index 0000000..afb43c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CallbackControllerAutoAddableTest : SysuiTestCase() {
+
+ @Test
+ fun callbackAddedAndRemoved() = runTest {
+ val controller = TestableController()
+ val callback = object : TestableController.Callback {}
+ val underTest =
+ object :
+ CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+ controller
+ ) {
+ override val description: String = ""
+ override val spec: TileSpec
+ get() = SPEC
+
+ override fun ProducerScope<AutoAddSignal>.getCallback():
+ TestableController.Callback {
+ return callback
+ }
+ }
+
+ val job = launch { underTest.autoAddSignal(0).collect {} }
+ runCurrent()
+ assertThat(controller.callbacks).containsExactly(callback)
+ job.cancel()
+ runCurrent()
+ assertThat(controller.callbacks).isEmpty()
+ }
+
+ @Test
+ fun sendAddFromCallback() = runTest {
+ val controller = TestableController()
+ val underTest =
+ object :
+ CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+ controller
+ ) {
+ override val description: String = ""
+
+ override val spec: TileSpec
+ get() = SPEC
+
+ override fun ProducerScope<AutoAddSignal>.getCallback():
+ TestableController.Callback {
+ return object : TestableController.Callback {
+ override fun change() {
+ sendAdd()
+ }
+ }
+ }
+ }
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ assertThat(signal).isNull()
+
+ controller.callbacks.first().change()
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun strategyIfNotAdded() {
+ val underTest =
+ object :
+ CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+ TestableController()
+ ) {
+ override val description: String = ""
+ override val spec: TileSpec
+ get() = SPEC
+
+ override fun ProducerScope<AutoAddSignal>.getCallback():
+ TestableController.Callback {
+ return object : TestableController.Callback {}
+ }
+ }
+
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+ }
+
+ private class TestableController : CallbackController<TestableController.Callback> {
+
+ val callbacks = mutableSetOf<Callback>()
+
+ override fun addCallback(listener: Callback) {
+ callbacks.add(listener)
+ }
+
+ override fun removeCallback(listener: Callback) {
+ callbacks.remove(listener)
+ }
+
+ interface Callback {
+ fun change() {}
+ }
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create("test")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
new file mode 100644
index 0000000..a357dad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CastAutoAddableTest : SysuiTestCase() {
+
+ @Mock private lateinit var castController: CastController
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<CastController.Callback>
+
+ private lateinit var underTest: CastAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = CastAutoAddable(castController)
+ }
+
+ @Test
+ fun onCastDevicesChanged_noDevices_noSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(castController).addCallback(capture(callbackCaptor))
+
+ callbackCaptor.value.onCastDevicesChanged()
+
+ assertThat(signal).isNull()
+ }
+
+ @Test
+ fun onCastDevicesChanged_deviceNotConnectedOrConnecting_noSignal() = runTest {
+ val device =
+ CastController.CastDevice().apply {
+ state = CastController.CastDevice.STATE_DISCONNECTED
+ }
+ whenever(castController.castDevices).thenReturn(listOf(device))
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(castController).addCallback(capture(callbackCaptor))
+
+ callbackCaptor.value.onCastDevicesChanged()
+
+ assertThat(signal).isNull()
+ }
+
+ @Test
+ fun onCastDevicesChanged_someDeviceConnecting_addSignal() = runTest {
+ val disconnectedDevice =
+ CastController.CastDevice().apply {
+ state = CastController.CastDevice.STATE_DISCONNECTED
+ }
+ val connectingDevice =
+ CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTING }
+ whenever(castController.castDevices)
+ .thenReturn(listOf(disconnectedDevice, connectingDevice))
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(castController).addCallback(capture(callbackCaptor))
+
+ callbackCaptor.value.onCastDevicesChanged()
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun onCastDevicesChanged_someDeviceConnected_addSignal() = runTest {
+ val disconnectedDevice =
+ CastController.CastDevice().apply {
+ state = CastController.CastDevice.STATE_DISCONNECTED
+ }
+ val connectedDevice =
+ CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTED }
+ whenever(castController.castDevices).thenReturn(listOf(disconnectedDevice, connectedDevice))
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(castController).addCallback(capture(callbackCaptor))
+
+ callbackCaptor.value.onCastDevicesChanged()
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(CastTile.TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
new file mode 100644
index 0000000..098ffc3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DataSaverAutoAddableTest : SysuiTestCase() {
+
+ @Mock private lateinit var dataSaverController: DataSaverController
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<DataSaverController.Listener>
+
+ private lateinit var underTest: DataSaverAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = DataSaverAutoAddable(dataSaverController)
+ }
+
+ @Test
+ fun dataSaverNotEnabled_NoSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+ callbackCaptor.value.onDataSaverChanged(false)
+
+ assertThat(signal).isNull()
+ }
+
+ @Test
+ fun dataSaverEnabled_addSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+ callbackCaptor.value.onDataSaverChanged(true)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(DataSaverTile.TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
new file mode 100644
index 0000000..a2e3538
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DeviceControlsAutoAddableTest : SysuiTestCase() {
+
+ @Mock private lateinit var deviceControlsController: DeviceControlsController
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<DeviceControlsController.Callback>
+
+ private lateinit var underTest: DeviceControlsAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = DeviceControlsAutoAddable(deviceControlsController)
+ }
+
+ @Test
+ fun strategyAlways() {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+ }
+
+ @Test
+ fun onControlsUpdate_position_addSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ val position = 5
+ runCurrent()
+
+ verify(deviceControlsController).setCallback(capture(callbackCaptor))
+ callbackCaptor.value.onControlsUpdate(position)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+ verify(deviceControlsController).removeCallback()
+ }
+
+ @Test
+ fun onControlsUpdate_nullPosition_noAddSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(deviceControlsController).setCallback(capture(callbackCaptor))
+ callbackCaptor.value.onControlsUpdate(null)
+
+ assertThat(signal).isNull()
+ verify(deviceControlsController).removeCallback()
+ }
+
+ @Test
+ fun onRemoveControlsAutoTracker_removeSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(deviceControlsController).setCallback(capture(callbackCaptor))
+ callbackCaptor.value.removeControlsAutoTracker()
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun flowCancelled_removeCallback() = runTest {
+ val job = launch { underTest.autoAddSignal(0).collect() }
+ runCurrent()
+
+ job.cancel()
+ runCurrent()
+ verify(deviceControlsController).removeCallback()
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
new file mode 100644
index 0000000..ee96b47
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HotspotAutoAddableTest : SysuiTestCase() {
+
+ @Mock private lateinit var hotspotController: HotspotController
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<HotspotController.Callback>
+
+ private lateinit var underTest: HotspotAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = HotspotAutoAddable(hotspotController)
+ }
+
+ @Test
+ fun enabled_addSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(hotspotController).addCallback(capture(callbackCaptor))
+ callbackCaptor.value.onHotspotChanged(/* enabled = */ true, /* numDevices = */ 5)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun notEnabled_noSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(hotspotController).addCallback(capture(callbackCaptor))
+ callbackCaptor.value.onHotspotChanged(/* enabled = */ false, /* numDevices = */ 0)
+
+ assertThat(signal).isNull()
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(HotspotTile.TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
new file mode 100644
index 0000000..e03072a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.hardware.display.NightDisplayListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NightDisplayAutoAddableTest : SysuiTestCase() {
+
+ @Mock(answer = Answers.RETURNS_SELF)
+ private lateinit var nightDisplayListenerBuilder: NightDisplayListenerModule.Builder
+ @Mock private lateinit var nightDisplayListener: NightDisplayListener
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<NightDisplayListener.Callback>
+
+ private lateinit var underTest: NightDisplayAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(nightDisplayListenerBuilder.build()).thenReturn(nightDisplayListener)
+ }
+
+ @Test
+ fun disabled_strategyDisabled() =
+ testWithFeatureAvailability(enabled = false) {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+ }
+
+ @Test
+ fun enabled_strategyIfNotAdded() = testWithFeatureAvailability {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+ }
+
+ @Test
+ fun listenerCreatedForCorrectUser() = testWithFeatureAvailability {
+ val user = 42
+ backgroundScope.launch { underTest.autoAddSignal(user).collect() }
+ runCurrent()
+
+ val inOrder = inOrder(nightDisplayListenerBuilder)
+ inOrder.verify(nightDisplayListenerBuilder).setUser(user)
+ inOrder.verify(nightDisplayListenerBuilder).build()
+ }
+
+ @Test
+ fun onCancelFlow_removeCallback() = testWithFeatureAvailability {
+ val job = launch { underTest.autoAddSignal(0).collect() }
+ runCurrent()
+ job.cancel()
+ runCurrent()
+ verify(nightDisplayListener).setCallback(null)
+ }
+
+ @Test
+ fun onActivatedTrue_addSignal() = testWithFeatureAvailability {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(nightDisplayListener).setCallback(capture(callbackCaptor))
+ callbackCaptor.value.onActivated(true)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ private fun testWithFeatureAvailability(
+ enabled: Boolean = true,
+ body: suspend TestScope.() -> TestResult
+ ) = runTest {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_nightDisplayAvailable,
+ enabled
+ )
+ underTest = NightDisplayAutoAddable(nightDisplayListenerBuilder, context)
+ body()
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(NightDisplayTile.TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
new file mode 100644
index 0000000..7b4a55e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
+
+ @Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
+ @Captor
+ private lateinit var reduceBrightColorsListenerCaptor:
+ ArgumentCaptor<ReduceBrightColorsController.Listener>
+
+ private lateinit var underTest: ReduceBrightColorsAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun notAvailable_strategyDisabled() =
+ testWithFeatureAvailability(available = false) {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+ }
+
+ @Test
+ fun available_strategyIfNotAdded() =
+ testWithFeatureAvailability(available = true) {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+ }
+
+ @Test
+ fun activated_addSignal() = testWithFeatureAvailability {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+ reduceBrightColorsListenerCaptor.value.onActivated(true)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun notActivated_noSignal() = testWithFeatureAvailability {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+ reduceBrightColorsListenerCaptor.value.onActivated(false)
+
+ assertThat(signal).isNull()
+ }
+
+ private fun testWithFeatureAvailability(
+ available: Boolean = true,
+ body: suspend TestScope.() -> TestResult
+ ) = runTest {
+ underTest = ReduceBrightColorsAutoAddable(reduceBrightColorsController, available)
+ body()
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
new file mode 100644
index 0000000..fb35a3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SafetyCenterAutoAddableTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Mock private lateinit var safetyController: SafetyController
+ @Mock private lateinit var packageManager: PackageManager
+ @Captor
+ private lateinit var safetyControllerListenerCaptor: ArgumentCaptor<SafetyController.Listener>
+
+ private lateinit var underTest: SafetyCenterAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.ensureTestableResources()
+
+ // Set these by default, will also test special cases
+ context.orCreateTestableResources.addOverride(
+ R.string.safety_quick_settings_tile_class,
+ SAFETY_TILE_CLASS_NAME
+ )
+ whenever(packageManager.permissionControllerPackageName)
+ .thenReturn(PERMISSION_CONTROLLER_PACKAGE_NAME)
+
+ underTest =
+ SafetyCenterAutoAddable(
+ safetyController,
+ packageManager,
+ context.resources,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun strategyAlwaysTrack() =
+ testScope.runTest {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+ }
+
+ @Test
+ fun tileAlwaysAdded() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun safetyCenterDisabled_removeSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+ safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(false)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun safetyCenterEnabled_newAddSignal() =
+ testScope.runTest {
+ val signals by collectValues(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+ safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(true)
+
+ assertThat(signals.size).isEqualTo(2)
+ assertThat(signals.last()).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun flowCancelled_removeListener() =
+ testScope.runTest {
+ val job = launch { underTest.autoAddSignal(0).collect() }
+ runCurrent()
+
+ verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+
+ job.cancel()
+ runCurrent()
+ verify(safetyController).removeCallback(safetyControllerListenerCaptor.value)
+ }
+
+ @Test
+ fun emptyClassName_noSignals() =
+ testScope.runTest {
+ context.orCreateTestableResources.addOverride(
+ R.string.safety_quick_settings_tile_class,
+ ""
+ )
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+ runCurrent()
+
+ verify(safetyController, never()).addCallback(any())
+
+ assertThat(signal).isNull()
+ }
+
+ companion object {
+ private const val SAFETY_TILE_CLASS_NAME = "cls"
+ private const val PERMISSION_CONTROLLER_PACKAGE_NAME = "pkg"
+ private val SPEC =
+ TileSpec.create(
+ ComponentName(PERMISSION_CONTROLLER_PACKAGE_NAME, SAFETY_TILE_CLASS_NAME)
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
new file mode 100644
index 0000000..6b250f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WalletAutoAddableTest : SysuiTestCase() {
+
+ @Mock private lateinit var walletController: WalletController
+
+ private lateinit var underTest: WalletAutoAddable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = WalletAutoAddable(walletController)
+ }
+
+ @Test
+ fun strategyIfNotAdded() {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+ }
+
+ @Test
+ fun walletPositionNull_noSignal() = runTest {
+ whenever(walletController.getWalletPosition()).thenReturn(null)
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ assertThat(signal).isNull()
+ }
+
+ @Test
+ fun walletPositionNumber_addedInThatPosition() = runTest {
+ val position = 4
+ whenever(walletController.getWalletPosition()).thenReturn(4)
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
new file mode 100644
index 0000000..e9f7c8ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_FULL
+import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
+import android.content.pm.UserInfo.FLAG_PRIMARY
+import android.content.pm.UserInfo.FLAG_PROFILE
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.FakeUserTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WorkTileAutoAddableTest : SysuiTestCase() {
+
+ private lateinit var userTracker: FakeUserTracker
+
+ private lateinit var underTest: WorkTileAutoAddable
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ userTracker =
+ FakeUserTracker(
+ _userId = USER_INFO_0.id,
+ _userInfo = USER_INFO_0,
+ _userProfiles = listOf(USER_INFO_0)
+ )
+
+ underTest = WorkTileAutoAddable(userTracker)
+ }
+
+ @Test
+ fun changeInProfiles_hasManagedProfile_sendsAddSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun changeInProfiles_noManagedProfile_sendsRemoveSignal() = runTest {
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ userTracker.set(listOf(USER_INFO_0), selectedUserIndex = 0)
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun startingWithManagedProfile_sendsAddSignal() = runTest {
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun userChangeToUserWithProfile_noSignalForOriginalUser() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ userTracker.set(listOf(USER_INFO_1, USER_INFO_WORK), selectedUserIndex = 0)
+
+ assertThat(signal).isNotEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun userChangeToUserWithoutProfile_noSignalForOriginalUser() = runTest {
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ userTracker.set(listOf(USER_INFO_1), selectedUserIndex = 0)
+
+ assertThat(signal).isNotEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun strategyAlways() {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
+ private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
+ private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
+ private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
new file mode 100644
index 0000000..f924b35
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
+import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AutoAddInteractorTest : SysuiTestCase() {
+ private val testScope = TestScope()
+
+ private val autoAddRepository = FakeAutoAddRepository()
+
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var currentTilesInteractor: CurrentTilesInteractor
+ @Mock private lateinit var logger: QSPipelineLogger
+ private lateinit var underTest: AutoAddInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(currentTilesInteractor.userId).thenReturn(MutableStateFlow(USER))
+ }
+
+ @Test
+ fun autoAddable_alwaysTrack_addSignal_tileAddedAndMarked() =
+ testScope.runTest {
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+
+ val position = 3
+ fakeAutoAddable.sendAddSignal(USER, position)
+ runCurrent()
+
+ verify(currentTilesInteractor).addTile(SPEC, position)
+ assertThat(autoAddedTiles).contains(SPEC)
+ }
+
+ @Test
+ fun autoAddable_alwaysTrack_addThenRemoveSignal_tileAddedAndRemoved() =
+ testScope.runTest {
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+
+ val position = 3
+ fakeAutoAddable.sendAddSignal(USER, position)
+ runCurrent()
+ fakeAutoAddable.sendRemoveSignal(USER)
+ runCurrent()
+
+ val inOrder = inOrder(currentTilesInteractor)
+ inOrder.verify(currentTilesInteractor).addTile(SPEC, position)
+ inOrder.verify(currentTilesInteractor).removeTiles(setOf(SPEC))
+ assertThat(autoAddedTiles).doesNotContain(SPEC)
+ }
+
+ @Test
+ fun autoAddable_alwaysTrack_addSignalWhenAddedPreviously_noop() =
+ testScope.runTest {
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+ autoAddRepository.markTileAdded(USER, SPEC)
+ runCurrent()
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+
+ val position = 3
+ fakeAutoAddable.sendAddSignal(USER, position)
+ runCurrent()
+
+ verify(currentTilesInteractor, never()).addTile(SPEC, position)
+ }
+
+ @Test
+ fun autoAddable_disabled_noInteractionsWithCurrentTilesInteractor() =
+ testScope.runTest {
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Disabled)
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+
+ val position = 3
+ fakeAutoAddable.sendAddSignal(USER, position)
+ runCurrent()
+ fakeAutoAddable.sendRemoveSignal(USER)
+ runCurrent()
+
+ verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+ verify(currentTilesInteractor, never()).removeTiles(any())
+ assertThat(autoAddedTiles).doesNotContain(SPEC)
+ }
+
+ @Test
+ fun autoAddable_trackIfNotAdded_removeSignal_noop() =
+ testScope.runTest {
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+ runCurrent()
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+
+ fakeAutoAddable.sendRemoveSignal(USER)
+ runCurrent()
+
+ verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+ verify(currentTilesInteractor, never()).removeTiles(any())
+ }
+
+ @Test
+ fun autoAddable_trackIfNotAdded_addSignalWhenPreviouslyAdded_noop() =
+ testScope.runTest {
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+ autoAddRepository.markTileAdded(USER, SPEC)
+ runCurrent()
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+
+ fakeAutoAddable.sendAddSignal(USER)
+ runCurrent()
+
+ verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+ verify(currentTilesInteractor, never()).removeTiles(any())
+ }
+
+ @Test
+ fun autoAddable_trackIfNotAdded_addSignal_addedAndMarked() =
+ testScope.runTest {
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+
+ val position = 3
+ fakeAutoAddable.sendAddSignal(USER, position)
+ runCurrent()
+
+ verify(currentTilesInteractor).addTile(SPEC, position)
+ assertThat(autoAddedTiles).contains(SPEC)
+ }
+
+ private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
+ return AutoAddInteractor(
+ autoAddables,
+ autoAddRepository,
+ dumpManager,
+ logger,
+ testScope.backgroundScope
+ )
+ .apply { init(currentTilesInteractor) }
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create("spec")
+ private val USER = 10
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index e7ad489..30cea2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -100,6 +100,7 @@
MockitoAnnotations.initMocks(this)
featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
+ featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 1f9ec94..c85c8ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -72,7 +72,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -87,7 +87,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index aaa0816..5d2d192 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -73,7 +73,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -85,7 +85,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -97,7 +97,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.unlockDevice()
+ utils.authenticationRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -112,7 +112,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin(1234)
)
- authenticationInteractor.lockDevice()
+ utils.authenticationRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index e680a4e..e4a2236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -34,6 +34,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -51,6 +52,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.NightDisplayListenerModule;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSHost;
@@ -111,6 +113,8 @@
@Mock private DataSaverController mDataSaverController;
@Mock private ManagedProfileController mManagedProfileController;
@Mock private NightDisplayListener mNightDisplayListener;
+ @Mock(answer = Answers.RETURNS_SELF)
+ private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
@Mock private ReduceBrightColorsController mReduceBrightColorsController;
@Mock private DeviceControlsController mDeviceControlsController;
@Mock private WalletController mWalletController;
@@ -151,6 +155,7 @@
.thenReturn(TEST_CUSTOM_SAFETY_PKG);
Context context = Mockito.spy(mContext);
when(context.getPackageManager()).thenReturn(mPackageManager);
+ when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener);
mAutoTileManager = createAutoTileManager(context);
mAutoTileManager.init();
@@ -167,7 +172,7 @@
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
- NightDisplayListener nightDisplayListener,
+ NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
@@ -180,7 +185,7 @@
hotspotController,
dataSaverController,
managedProfileController,
- nightDisplayListener,
+ mNightDisplayListenerBuilder,
castController,
reduceBrightColorsController,
deviceControlsController,
@@ -191,7 +196,7 @@
private AutoTileManager createAutoTileManager(Context context) {
return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
- mDataSaverController, mManagedProfileController, mNightDisplayListener,
+ mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder,
mCastController, mReduceBrightColorsController, mDeviceControlsController,
mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
}
@@ -204,7 +209,7 @@
HotspotController hC = mock(HotspotController.class);
DataSaverController dSC = mock(DataSaverController.class);
ManagedProfileController mPC = mock(ManagedProfileController.class);
- NightDisplayListener nDS = mock(NightDisplayListener.class);
+ NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class);
CastController cC = mock(CastController.class);
ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
DeviceControlsController dCC = mock(DeviceControlsController.class);
@@ -212,14 +217,14 @@
SafetyController sC = mock(SafetyController.class);
AutoTileManager manager =
- createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC,
+ createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC,
dCC, wC, sC, true);
verify(tracker, never()).initialize();
verify(hC, never()).addCallback(any());
verify(dSC, never()).addCallback(any());
verify(mPC, never()).addCallback(any());
- verify(nDS, never()).setCallback(any());
+ verifyNoMoreInteractions(nDSB);
verify(cC, never()).addCallback(any());
verify(rBC, never()).addCallback(any());
verify(dCC, never()).setCallback(any());
@@ -615,6 +620,15 @@
createAutoTileManager(mContext).destroy();
}
+ @Test
+ public void testUserChange_newNightDisplayListenerCreated() {
+ UserHandle newUser = UserHandle.of(1000);
+ mAutoTileManager.changeUser(newUser);
+ InOrder inOrder = inOrder(mNightDisplayListenerBuilder);
+ inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier());
+ inOrder.verify(mNightDisplayListenerBuilder).build();
+ }
+
// Will only notify if it's listening
private void changeValue(String key, int value) {
mSecureSettings.putIntForUser(key, value, USER);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 6b18169..85fbef0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -27,6 +27,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.screenrecord.RecordingController
@@ -46,9 +48,17 @@
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.util.RingerModeTracker
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.DateFormatUtil
import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,6 +67,8 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -64,11 +76,13 @@
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class PhoneStatusBarPolicyTest : SysuiTestCase() {
companion object {
private const val ALARM_SLOT = "alarm"
+ private const val CONNECTED_DISPLAY_SLOT = "connected_display"
}
@Mock private lateinit var iconController: StatusBarIconController
@@ -102,6 +116,9 @@
private lateinit var alarmCallbackCaptor:
ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+
private lateinit var executor: FakeExecutor
private lateinit var statusBarPolicy: PhoneStatusBarPolicy
private lateinit var testableLooper: TestableLooper
@@ -164,6 +181,57 @@
verify(iconController).setIconVisibility(ALARM_SLOT, true)
}
+ @Test
+ fun connectedDisplay_connected_iconShown() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+ runCurrent()
+
+ verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+ }
+
+ @Test
+ fun connectedDisplay_disconnected_iconHidden() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+
+ verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+ }
+
+ @Test
+ fun connectedDisplay_disconnectedThenConnected_iconShown() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+ fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+ fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+
+ inOrder(iconController).apply {
+ verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+ verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+ verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+ }
+ }
+
+ @Test
+ fun connectedDisplay_connectSecureDisplay_iconShown() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE)
+
+ verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+ }
+
private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
return AlarmManager.AlarmClockInfo(10L, null)
}
@@ -200,7 +268,16 @@
dateFormatUtil,
ringerModeTracker,
privacyItemController,
- privacyLogger
+ privacyLogger,
+ fakeConnectedDisplayStateProvider,
+ JavaAdapter(testScope.backgroundScope)
)
}
+
+ private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
+ private val flow = MutableSharedFlow<State>()
+ suspend fun emit(value: State) = flow.emit(value)
+ override val connectedDisplayState: Flow<State>
+ get() = flow
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 548e1b5..c7143de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -737,6 +737,16 @@
}
@Test
+ public void testResetBouncerAnimatingAway() {
+ reset(mPrimaryBouncerInteractor);
+ when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
+
+ mStatusBarKeyguardViewManager.reset(true);
+
+ verify(mPrimaryBouncerInteractor, never()).hide();
+ }
+
+ @Test
public void handleDispatchTouchEvent_alternateBouncerNotVisible() {
mStatusBarKeyguardViewManager.addCallback(mCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 9b1d93b..5dcb901 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -18,10 +18,6 @@
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -49,7 +45,7 @@
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
-import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorTestRule;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -85,6 +81,7 @@
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -139,6 +136,8 @@
private StatusBarWindowStateController mStatusBarWindowStateController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @ClassRule
+ public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
@@ -172,7 +171,6 @@
@Test
public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
- when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -192,24 +190,26 @@
public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
// GIVEN the status bar hides the system info via disable flags, while there is no event
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+ // WHEN the system event animation starts
+ fragment.onSystemEventAnimationBegin().start();
+
+ // THEN the view remains invisible during the animation
+ assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+ mAnimatorTestRule.advanceTimeBy(500);
+ assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+
// WHEN the disable flags are cleared during a system event animation
- when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- // THEN the view is made visible again, but still low alpha
- assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ // THEN the view remains invisible
assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
// WHEN the system event animation finishes
- when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
- Animator anim = fragment.onSystemEventAnimationFinish(false);
- anim.start();
- processAllMessages();
- anim.end();
+ fragment.onSystemEventAnimationFinish(false).start();
+ mAnimatorTestRule.advanceTimeBy(500);
// THEN the system info is full alpha
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -219,20 +219,15 @@
public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
// GIVEN the status bar hides the system info via disable flags, while there is no event
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
// WHEN the system event animation finishes
- when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
- Animator anim = fragment.onSystemEventAnimationFinish(false);
- anim.start();
- processAllMessages();
- anim.end();
+ fragment.onSystemEventAnimationFinish(false).start();
+ mAnimatorTestRule.advanceTimeBy(500);
- // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is
- // still set)
- assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+ // THEN the system info remains invisible (since the disable flag is still set)
+ assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
@@ -241,15 +236,14 @@
public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
// GIVEN the status bar is not disabled
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
- // WHEN the system event animation begins
- Animator anim = fragment.onSystemEventAnimationBegin();
- anim.start();
- processAllMessages();
- anim.end();
+ assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
- // THEN the system info is visible but alpha 0
- assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ // WHEN the system event animation begins
+ fragment.onSystemEventAnimationBegin().start();
+ mAnimatorTestRule.advanceTimeBy(500);
+
+ // THEN the system info is invisible
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
}
@@ -257,25 +251,21 @@
public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
// GIVEN the status bar is not disabled
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
- // WHEN the system event animation begins
- Animator anim = fragment.onSystemEventAnimationBegin();
- anim.start();
- processAllMessages();
- anim.end();
+ assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
- // THEN the system info is visible but alpha 0
- assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ // WHEN the system event animation begins
+ fragment.onSystemEventAnimationBegin().start();
+ mAnimatorTestRule.advanceTimeBy(500);
+
+ // THEN the system info is invisible
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
// WHEN the system event animation finishes
- when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
- anim = fragment.onSystemEventAnimationFinish(false);
- anim.start();
- processAllMessages();
- anim.end();
+ fragment.onSystemEventAnimationFinish(false).start();
+ mAnimatorTestRule.advanceTimeBy(500);
- // THEN the syste info is full alpha and VISIBLE
+ // THEN the system info is full alpha and VISIBLE
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
new file mode 100644
index 0000000..2617613
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_SOURCE_1 = 1
+private const val TEST_SOURCE_2 = 2
+private const val TEST_ANIMATION_DURATION = 100L
+private const val INITIAL_ALPHA = 1f
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class MultiSourceMinAlphaControllerTest : SysuiTestCase() {
+
+ private val view = View(context)
+ private val multiSourceMinAlphaController =
+ MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA)
+
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
+ @Before
+ fun setup() {
+ multiSourceMinAlphaController.reset()
+ }
+
+ @Test
+ fun testSetAlpha() {
+ multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_1)
+ assertEquals(0.5f, view.alpha)
+ }
+
+ @Test
+ fun testAnimateToAlpha() {
+ multiSourceMinAlphaController.animateToAlpha(
+ alpha = 0.5f,
+ sourceId = TEST_SOURCE_1,
+ duration = TEST_ANIMATION_DURATION
+ )
+ animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+ assertEquals(0.5f, view.alpha)
+ }
+
+ @Test
+ fun testReset() {
+ multiSourceMinAlphaController.animateToAlpha(
+ alpha = 0.5f,
+ sourceId = TEST_SOURCE_1,
+ duration = TEST_ANIMATION_DURATION
+ )
+ multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2)
+ multiSourceMinAlphaController.reset()
+ // advance time to ensure that animators are cancelled when the controller is reset
+ animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+ assertEquals(INITIAL_ALPHA, view.alpha)
+ }
+
+ @Test
+ fun testMinOfTwoSourcesIsApplied() {
+ multiSourceMinAlphaController.setAlpha(alpha = 0f, sourceId = TEST_SOURCE_1)
+ multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_2)
+ assertEquals(0f, view.alpha)
+ multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+ assertEquals(0.5f, view.alpha)
+ }
+
+ @Test
+ fun testSetAlphaForSameSourceCancelsAnimator() {
+ multiSourceMinAlphaController.animateToAlpha(
+ alpha = 0f,
+ sourceId = TEST_SOURCE_1,
+ duration = TEST_ANIMATION_DURATION
+ )
+ animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+ multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+ animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+ // verify that animation was cancelled and the setAlpha call overrides the alpha value of
+ // the animation
+ assertEquals(1f, view.alpha)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index a12393e..a718f70 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -18,12 +18,18 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
class FakeAuthenticationRepository(
private val delegate: AuthenticationRepository,
private val onSecurityModeChanged: (SecurityMode) -> Unit,
) : AuthenticationRepository by delegate {
+ private val _isUnlocked = MutableStateFlow(false)
+ override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
private var authenticationMethod: AuthenticationMethodModel = DEFAULT_AUTHENTICATION_METHOD
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
@@ -35,6 +41,10 @@
onSecurityModeChanged(authenticationMethod.toSecurityMode())
}
+ fun setUnlocked(isUnlocked: Boolean) {
+ _isUnlocked.value = isUnlocked
+ }
+
companion object {
val DEFAULT_AUTHENTICATION_METHOD =
AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
new file mode 100644
index 0000000..9ea079f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAutoAddRepository : AutoAddRepository {
+
+ private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>()
+
+ override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+ return getFlow(userId)
+ }
+
+ override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
+ if (spec == TileSpec.Invalid) return
+ with(getFlow(userId)) { value = value.toMutableSet().apply { add(spec) } }
+ }
+
+ override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
+ with(getFlow(userId)) { value = value.toMutableSet().apply { remove(spec) } }
+ }
+
+ private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> =
+ autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
new file mode 100644
index 0000000..ebdd6fd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeAutoAddable(
+ private val spec: TileSpec,
+ override val autoAddTracking: AutoAddTracking,
+) : AutoAddable {
+
+ private val signalsPerUser = mutableMapOf<Int, MutableStateFlow<AutoAddSignal?>>()
+ private fun getFlow(userId: Int): MutableStateFlow<AutoAddSignal?> =
+ signalsPerUser.getOrPut(userId) { MutableStateFlow(null) }
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return getFlow(userId).asStateFlow().filterNotNull()
+ }
+
+ suspend fun sendRemoveSignal(userId: Int) {
+ getFlow(userId).value = AutoAddSignal.Remove(spec)
+ }
+
+ suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
+ getFlow(userId).value = AutoAddSignal.Add(spec, position)
+ }
+
+ override val description: String
+ get() = "FakeAutoAddable($spec)"
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 6f228f4..0b6e2a2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -26,6 +26,7 @@
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -55,10 +56,12 @@
FakeAuthenticationRepository(
delegate =
AuthenticationRepositoryImpl(
+ applicationScope = applicationScope(),
getSecurityMode = { securityMode },
backgroundDispatcher = testDispatcher,
userRepository = FakeUserRepository(),
lockPatternUtils = mock(),
+ keyguardRepository = FakeKeyguardRepository(),
),
onSecurityModeChanged = { securityMode = it },
)
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ae24f1e..de6522e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1027,7 +1027,7 @@
private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
"enable_wait_for_finish_attach_application";
- private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
+ private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
/** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
public volatile boolean mEnableWaitForFinishAttachApplication =
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index 969a174..aeb6b6e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.RemoteException;
@@ -44,7 +43,6 @@
@NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
@NonNull private final Optional<ISidefpsController> mSidefpsController;
- @NonNull private final Optional<IUdfpsOverlay> mUdfpsOverlay;
/**
* Create an overlay controller for each modality.
@@ -54,11 +52,9 @@
*/
public SensorOverlays(
@Nullable IUdfpsOverlayController udfpsOverlayController,
- @Nullable ISidefpsController sidefpsController,
- @Nullable IUdfpsOverlay udfpsOverlay) {
+ @Nullable ISidefpsController sidefpsController) {
mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
mSidefpsController = Optional.ofNullable(sidefpsController);
- mUdfpsOverlay = Optional.ofNullable(udfpsOverlay);
}
/**
@@ -94,14 +90,6 @@
Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
}
}
-
- if (mUdfpsOverlay.isPresent()) {
- try {
- mUdfpsOverlay.get().show(client.getRequestId(), sensorId, reason);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when showing the new UDFPS overlay", e);
- }
- }
}
/**
@@ -125,14 +113,6 @@
Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
}
}
-
- if (mUdfpsOverlay.isPresent()) {
- try {
- mUdfpsOverlay.get().hide(sensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when hiding the new udfps overlay", e);
- }
- }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index ea6bb62..28cb7d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -54,7 +54,6 @@
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
@@ -963,16 +962,6 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
- public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
- super.setUdfpsOverlay_enforcePermission();
-
- for (ServiceProvider provider : mRegistry.getProviders()) {
- provider.setUdfpsOverlay(controller);
- }
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- @Override
public void onPowerPressed() {
super.onPowerPressed_enforcePermission();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index d70ca8c..a15d1a4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,7 +28,6 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -134,12 +133,6 @@
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
- /**
- * Sets udfps overlay
- * @param controller udfps overlay
- */
- void setUdfpsOverlay(@NonNull IUdfpsOverlay controller);
-
void onPowerPressed();
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 3fc36b6..54d1faa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -30,7 +30,6 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.Handler;
@@ -112,7 +111,6 @@
@NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
- @Nullable IUdfpsOverlay udfpsOverlay,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler,
@@ -137,8 +135,7 @@
false /* shouldVibrate */,
biometricStrength);
setRequestId(requestId);
- mSensorOverlays = new SensorOverlays(udfpsOverlayController,
- sidefpsController, udfpsOverlay);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 46f62d3..51a9385 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -22,7 +22,6 @@
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -59,15 +58,13 @@
@NonNull FingerprintAuthenticateOptions options,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
- @Nullable IUdfpsOverlay udfpsOverlay,
boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, options.getUserId(),
options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController,
- null /* sideFpsController*/, udfpsOverlay);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
mOptions = options;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index d35469c..f9e08d6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -29,7 +29,6 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
@@ -87,7 +86,6 @@
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
- @Nullable IUdfpsOverlay udfpsOverlay,
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -95,8 +93,7 @@
biometricContext);
setRequestId(requestId);
mSensorProps = sensorProps;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController,
- sidefpsController, udfpsOverlay);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser;
mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index f8d2566..0421d78 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -43,7 +43,6 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Handler;
@@ -122,7 +121,6 @@
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
- @Nullable private IUdfpsOverlay mUdfpsOverlay;
private AuthSessionCoordinator mAuthSessionCoordinator;
private final class BiometricTaskStackListener extends TaskStackListener {
@@ -420,7 +418,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
mFingerprintSensors.get(sensorId).getSensorProperties(),
- mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+ mUdfpsOverlayController, mSidefpsController,
maxTemplatesPerUser, enrollReason);
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
mBiometricStateCallback, new ClientMonitorCallback() {
@@ -458,8 +456,7 @@
mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
options,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
- mBiometricContext,
- mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
+ mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -483,7 +480,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
- mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+ mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication,
mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
Utils.getCurrentStrength(sensorId),
@@ -719,11 +716,6 @@
}
@Override
- public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
- mUdfpsOverlay = controller;
- }
-
- @Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
if (mFingerprintSensors.contains(sensorId)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 1cbbf89..92b216d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -40,7 +40,6 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -123,7 +122,6 @@
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
- @Nullable private IUdfpsOverlay mUdfpsOverlay;
@NonNull private final BiometricContext mBiometricContext;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@@ -597,9 +595,7 @@
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN),
- mBiometricContext,
- mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
- enrollReason);
+ mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -644,8 +640,7 @@
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mLazyDaemon, token, id, listener, options,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
- mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
- isStrongBiometric);
+ mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -668,7 +663,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
- mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+ mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication, mSensorProperties,
Utils.getCurrentStrength(mSensorId));
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
@@ -856,11 +851,6 @@
}
@Override
- public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
- mUdfpsOverlay = controller;
- }
-
- @Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 2a62338..9966e91 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -30,7 +30,6 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -83,7 +82,6 @@
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
- @Nullable IUdfpsOverlay udfpsOverlay,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@Authenticators.Types int sensorStrength) {
@@ -93,8 +91,7 @@
false /* shouldVibrate */, sensorStrength);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController,
- sidefpsController, udfpsOverlay);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index ed0a201..0d7f9f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -26,7 +26,6 @@
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -67,13 +66,12 @@
@NonNull FingerprintAuthenticateOptions options,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
- @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
+ boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, options.getUserId(),
options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
- mSensorOverlays = new SensorOverlays(udfpsOverlayController,
- null /* sideFpsController */, udfpsOverlay);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
mIsStrongBiometric = isStrongBiometric;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c2b7944..6fee84a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -27,7 +27,6 @@
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -69,14 +68,12 @@
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
- @Nullable IUdfpsOverlay udfpsOverlay,
@FingerprintManager.EnrollReason int enrollReason) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
biometricContext);
setRequestId(requestId);
- mSensorOverlays = new SensorOverlays(udfpsOverlayController,
- sidefpsController, udfpsOverlay);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mEnrollReason = enrollReason;
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0292a99..b2d3fca 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -103,7 +103,7 @@
final int mTargetSdkVersion;
final int mOriginalFlags;
private final Context mContext;
- private final KeyguardManager mKeyguardManager;
+ private KeyguardManager mKeyguardManager;
private final PowerManager mPowerManager;
NotificationUsageStats.SingleNotificationStats stats;
boolean isCanceled;
@@ -1625,10 +1625,21 @@
}
boolean isLocked() {
- return mKeyguardManager.isKeyguardLocked()
+ return getKeyguardManager().isKeyguardLocked()
|| !mPowerManager.isInteractive(); // Unlocked AOD
}
+ /**
+ * For some early {@link NotificationRecord}, {@link KeyguardManager} can be {@code null} in
+ * the constructor. Retrieve it again if it is null.
+ */
+ private KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+ }
+ return mKeyguardManager;
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4a658d6..10ff3a3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -546,6 +546,7 @@
*/
private volatile long mLastStopAppSwitchesTime;
+ @GuardedBy("itself")
private final List<AnrController> mAnrController = new ArrayList<>();
IActivityController mController = null;
boolean mControllerIsAMonkey = false;
@@ -733,7 +734,7 @@
private boolean mShowDialogs = true;
/** Set if we are shutting down the system, similar to sleeping. */
- boolean mShuttingDown = false;
+ volatile boolean mShuttingDown;
/**
* We want to hold a wake lock while running a voice interaction session, since
@@ -2298,14 +2299,14 @@
/** Register an {@link AnrController} to control the ANR dialog behavior */
public void registerAnrController(AnrController controller) {
- synchronized (mGlobalLock) {
+ synchronized (mAnrController) {
mAnrController.add(controller);
}
}
/** Unregister an {@link AnrController} */
public void unregisterAnrController(AnrController controller) {
- synchronized (mGlobalLock) {
+ synchronized (mAnrController) {
mAnrController.remove(controller);
}
}
@@ -2321,7 +2322,7 @@
}
final ArrayList<AnrController> controllers;
- synchronized (mGlobalLock) {
+ synchronized (mAnrController) {
controllers = new ArrayList<>(mAnrController);
}
@@ -6034,15 +6035,13 @@
@Override
public boolean isShuttingDown() {
- synchronized (mGlobalLock) {
- return mShuttingDown;
- }
+ return mShuttingDown;
}
@Override
public boolean shuttingDown(boolean booted, int timeout) {
+ mShuttingDown = true;
synchronized (mGlobalLock) {
- mShuttingDown = true;
mRootWindowContainer.prepareForShutdown();
updateEventDispatchingLocked(booted);
notifyTaskPersisterLocked(null, true);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index f7ccc0d..0273a30 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -16,7 +16,9 @@
package com.android.server.wm;
+import android.annotation.NonNull;
import android.annotation.UiThread;
+import android.annotation.WorkerThread;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -30,14 +32,18 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.IoThread;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -45,9 +51,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Manages warning dialogs shown during application lifecycle.
@@ -61,11 +65,12 @@
public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
- private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+ @GuardedBy("mPackageFlags")
+ private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>();
private final ActivityTaskManagerService mAtm;
private final Context mUiContext;
- private final ConfigHandler mHandler;
+ private final WriteConfigTask mWriteConfigTask;
private final UiHandler mUiHandler;
private final AtomicFile mConfigFile;
@@ -75,30 +80,20 @@
private DeprecatedAbiDialog mDeprecatedAbiDialog;
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
- private HashSet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
- new HashSet<>();
+ private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
+ new ArraySet<>();
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity);
}
- /**
- * Creates a new warning dialog manager.
- * <p>
- * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
- *
- * @param atm
- * @param uiContext
- * @param handler
- * @param uiHandler
- * @param systemDir
- */
+ /** Creates a new warning dialog manager. */
public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
Handler uiHandler, File systemDir) {
mAtm = atm;
mUiContext = uiContext;
- mHandler = new ConfigHandler(handler.getLooper());
+ mWriteConfigTask = new WriteConfigTask();
mUiHandler = new UiHandler(uiHandler.getLooper());
mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
@@ -256,8 +251,9 @@
mUiHandler.hideDialogsForPackage(name);
synchronized (mPackageFlags) {
- mPackageFlags.remove(name);
- mHandler.scheduleWrite();
+ if (mPackageFlags.remove(name) != null) {
+ mWriteConfigTask.schedule();
+ }
}
}
@@ -425,7 +421,7 @@
} else {
mPackageFlags.remove(name);
}
- mHandler.scheduleWrite();
+ mWriteConfigTask.schedule();
}
}
}
@@ -556,46 +552,30 @@
}
}
- /**
- * Handles messages on the ActivityTaskManagerService thread.
- */
- private final class ConfigHandler extends Handler {
- private static final int MSG_WRITE = 1;
-
- private static final int DELAY_MSG_WRITE = 10000;
-
- public ConfigHandler(Looper looper) {
- super(looper, null, true);
- }
+ private final class WriteConfigTask implements Runnable {
+ private static final long WRITE_CONFIG_DELAY_MS = 10000;
+ final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags =
+ new AtomicReference<>();
@Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_WRITE:
- writeConfigToFileAmsThread();
- break;
+ public void run() {
+ final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null);
+ if (packageFlags != null) {
+ writeConfigToFile(packageFlags);
}
}
- public void scheduleWrite() {
- removeMessages(MSG_WRITE);
- sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+ @GuardedBy("mPackageFlags")
+ void schedule() {
+ if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) {
+ IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS);
+ }
}
}
- /**
- * Writes the configuration file.
- * <p>
- * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
- * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
- */
- private void writeConfigToFileAmsThread() {
- // Create a shallow copy so that we don't have to synchronize on config.
- final HashMap<String, Integer> packageFlags;
- synchronized (mPackageFlags) {
- packageFlags = new HashMap<>(mPackageFlags);
- }
-
+ /** Writes the configuration file. */
+ @WorkerThread
+ private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) {
FileOutputStream fos = null;
try {
fos = mConfigFile.startWrite();
@@ -605,9 +585,9 @@
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
out.startTag(null, "packages");
- for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
- String pkg = entry.getKey();
- int mode = entry.getValue();
+ for (int i = 0; i < packageFlags.size(); i++) {
+ final String pkg = packageFlags.keyAt(i);
+ final int mode = packageFlags.valueAt(i);
if (mode == 0) {
continue;
}
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
index 353341e..82e02ca 100644
--- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
@@ -47,11 +47,14 @@
import com.android.internal.infra.AndroidFuture;
+import com.google.common.util.concurrent.Uninterruptibles;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
@@ -62,6 +65,8 @@
@Presubmit
public class GameSessionTrampolineActivityTest {
+ private static final Duration TEST_ACTIVITY_OPEN_DURATION = Duration.ofSeconds(5);
+
@Before
public void setUp() {
setAlwaysFinishActivities(false);
@@ -145,10 +150,15 @@
startTestActivityViaGameSessionTrampolineActivity() {
Intent testActivityIntent = new Intent();
testActivityIntent.setClass(getInstrumentation().getTargetContext(), TestActivity.class);
+ sleep(TEST_ACTIVITY_OPEN_DURATION);
return startGameSessionTrampolineActivity(testActivityIntent);
}
+ private static void sleep(Duration sleepDuration) {
+ Uninterruptibles.sleepUninterruptibly(sleepDuration.toMillis(), TimeUnit.MILLISECONDS);
+ }
+
private static AndroidFuture<GameSessionActivityResult> startGameSessionTrampolineActivity(
Intent targetIntent) {
AndroidFuture<GameSessionActivityResult> resultFuture = new AndroidFuture<>();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 21c9c75..5012335 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -61,7 +61,7 @@
@Test
public void noopWhenBothNull() {
- final SensorOverlays useless = new SensorOverlays(null, null, null);
+ final SensorOverlays useless = new SensorOverlays(null, null);
useless.show(SENSOR_ID, 2, null);
useless.hide(SENSOR_ID);
}
@@ -69,12 +69,12 @@
@Test
public void testProvidesUdfps() {
final List<IUdfpsOverlayController> udfps = new ArrayList<>();
- SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController, null);
+ SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
sensorOverlays.ifUdfps(udfps::add);
assertThat(udfps).isEmpty();
- sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController, null);
+ sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
sensorOverlays.ifUdfps(udfps::add);
assertThat(udfps).containsExactly(mUdfpsOverlayController);
}
@@ -96,7 +96,7 @@
private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
throws Exception {
- final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
@@ -126,7 +126,7 @@
private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
throws Exception {
- final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
sensorOverlays.hide(SENSOR_ID);
if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index c383a96..46af905 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -436,7 +436,7 @@
mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, null /* lockoutCache */,
- mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
+ mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
mSensorProps,
new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 6dfdd87..20d5f93 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -163,6 +163,6 @@
.setOpPackageName("a-test")
.build(),
mBiometricLogger, mBiometricContext,
- mUdfpsOverlayController, null, true /* isStrongBiometric */);
+ mUdfpsOverlayController, true /* isStrongBiometric */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 3c89278..ef25380 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -296,6 +296,6 @@
mClientMonitorCallbackConverter, 0 /* userId */,
HAT, "owner", mBiometricUtils, 8 /* sensorId */,
mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
- mSideFpsController, null, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+ mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 7fe8582..c508fa9 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -59,6 +59,8 @@
private String mDeviceName = "";
private String mDeviceDescription = "";
+ private boolean mHasJackDetect = true;
+
public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
boolean hasOutput, boolean hasInput,
boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) {
@@ -168,8 +170,14 @@
if (mJackDetector != null) {
return;
}
+ if (!mHasJackDetect) {
+ return;
+ }
// If no jack detect capabilities exist, mJackDetector will be null.
mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
+ if (mJackDetector == null) {
+ mHasJackDetect = false;
+ }
}
/** Stops a jack-detection thread. */
@@ -182,8 +190,8 @@
/** Start using this device as the selected USB Audio Device. */
public synchronized void start() {
- startInput();
startOutput();
+ startInput();
}
/** Start using this device as the selected USB input device. */
@@ -208,8 +216,8 @@
/** Stop using this device as the selected USB Audio Device. */
public synchronized void stop() {
- stopInput();
stopOutput();
+ stopInput();
}
/** Stop using this device as the selected USB input device. */