Merge "Fix the namespace for aconfig flag" into main
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 5e9fdfb..1e824a1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -6257,6 +6257,29 @@
* {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
* management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
* app is being put in a power-save allowlist.
+ * <p>
+ * Example arguments when user force-stops an app from Settings:
+ * <pre>
+ * noteAppRestrictionEnabled(
+ * "com.example.app",
+ * appUid,
+ * RESTRICTION_LEVEL_FORCE_STOPPED,
+ * true,
+ * RESTRICTION_REASON_USER,
+ * "settings",
+ * 0);
+ * </pre>
+ * Example arguments when app is put in restricted standby bucket for exceeding X hours of jobs:
+ * <pre>
+ * noteAppRestrictionEnabled(
+ * "com.example.app",
+ * appUid,
+ * RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+ * true,
+ * RESTRICTION_REASON_SYSTEM_HEALTH,
+ * "job_duration",
+ * X * 3600 * 1000L);
+ * </pre>
*
* @param packageName the package name of the app
* @param uid the uid of the app
@@ -6264,11 +6287,20 @@
* @param enabled whether the state is being applied or removed
* @param reason the reason for the restriction state change, from {@code RestrictionReason}
* @param subReason a string sub reason limited to 16 characters that specifies additional
- * information about the reason for restriction.
+ * information about the reason for restriction. This string must only contain
+ * reasons related to excessive system resource usage or in some cases,
+ * source of the restriction. This string must not contain any details that
+ * identify user behavior beyond their actions to restrict/unrestrict/launch
+ * apps in some way.
+ * Examples of system resource usage: wakelock, wakeups, mobile_data,
+ * binder_calls, memory, excessive_threads, excessive_cpu, gps_scans, etc.
+ * Examples of user actions: settings, notification, command_line, launch, etc.
+ *
* @param threshold for reasons that are due to exceeding some threshold, the threshold value
* must be specified. The unit of the threshold depends on the reason and/or
* subReason. For time, use milliseconds. For memory, use KB. For count, use
- * the actual count or normalized as per-hour. For power, use milliwatts. Etc.
+ * the actual count or if rate limited, normalized per-hour. For power,
+ * use milliwatts. For CPU, use mcycles.
*
* @hide
*/
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index e66f7fe..d8df447 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1270,4 +1270,16 @@
* @hide
*/
public abstract boolean shouldDelayHomeLaunch(int userId);
+
+ /**
+ * Add a startup timestamp to the most recent start of the specified process.
+ *
+ * @param key The {@link ApplicationStartInfo} start timestamp key of the timestamp to add.
+ * @param timestampNs The clock monotonic timestamp to add in nanoseconds.
+ * @param uid The UID of the process to add this timestamp to.
+ * @param pid The process id of the process to add this timestamp to.
+ * @param userId The userId in the multi-user environment.
+ */
+ public abstract void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid,
+ int userId);
}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index e3c367f8..63ffaa0 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -39,6 +39,13 @@
}
flag {
+ name: "check_autogroup_before_post"
+ namespace: "systemui"
+ description: "Does a check to see if notification should be autogrouped before posting, and if so groups before post."
+ bug: "330214226"
+}
+
+flag {
name: "visit_risky_uris"
namespace: "systemui"
description: "Guards the security fix that ensures all URIs in intents and Person.java are valid"
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 0e3217d..cb8eb83 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -73,13 +73,12 @@
public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED;
/**
- * The permission is denied. Applicable only to runtime and app op permissions.
+ * The permission is denied. Applicable only to runtime permissions.
*
* <p>Returned when:
* <ul>
* <li>the runtime permission is granted, but the corresponding app op is denied
* for runtime permissions.</li>
- * <li>the app ops is ignored for app op permissions.</li>
* </ul>
*
* @hide
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 55bb430..7e51cb0 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -112,7 +112,7 @@
public static final int PERMISSION_GRANTED = 0;
/**
- * The permission is denied. Applicable only to runtime and app op permissions.
+ * The permission is denied. Applicable only to runtime permissions.
* <p>
* The app isn't expecting the permission to be denied so that a "no-op" action should be taken,
* such as returning an empty result.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1cdcd20..0e7de48 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2463,22 +2463,22 @@
public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
synchronized (mH) {
- if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) {
+ final boolean hasServedByInputMethod = hasServedByInputMethodLocked(view);
+ if (!isFocusedAndWindowFocused && !hasServedByInputMethod) {
// Fail early if the view is not focused and not served
// to avoid logging many erroneous calls.
return false;
}
- }
- final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
- final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
- ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
- ImeTracker.forLatency().onRequestHide(statsToken,
- ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
- ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView",
- this, null /* icProto */);
- synchronized (mH) {
- if (!hasServedByInputMethodLocked(view)) {
+ final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
+ final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
+ ImeTracker.forLatency().onRequestHide(statsToken,
+ ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
+ ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView",
+ this, null /* icProto */);
+
+ if (!hasServedByInputMethod) {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
ImeTracker.forLatency().onShowFailed(statsToken,
ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
index 6cf241e..ddbb586 100644
--- a/core/java/com/android/internal/util/ProcFileReader.java
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -89,6 +89,12 @@
mTail -= count;
if (mTail == 0) {
fillBuf();
+
+ if (mTail > 0 && mBuffer[0] == ' ') {
+ // After filling the buffer, it contains more consecutive
+ // delimiters that need to be skipped.
+ consumeBuf(0);
+ }
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 77a9912..bfbfb3a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -178,6 +178,7 @@
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
+ <protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" />
<protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
<protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" />
diff --git a/core/res/res/drawable/ic_thread_network.xml b/core/res/res/drawable/ic_thread_network.xml
new file mode 100644
index 0000000..1d7608f
--- /dev/null
+++ b/core/res/res/drawable/ic_thread_network.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M476,880Q394,879 322,847.5Q250,816 196,761.5Q142,707 111,634.5Q80,562 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,623 791.5,732.5Q703,842 563,871L563,480L607,480Q661,480 699.5,441.5Q738,403 738,349Q738,295 699.5,256.5Q661,218 607,218Q553,218 514.5,256.5Q476,295 476,349L476,393L345,393Q279,393 233,439Q187,485 187,551Q187,617 233,662.5Q279,708 345,708L345,621Q316,621 295,600.5Q274,580 274,551Q274,522 295,501Q316,480 345,480L476,480L476,880ZM563,393L563,349Q563,331 576,318Q589,305 607,305Q625,305 638,318Q651,331 651,349Q651,367 638,380Q625,393 607,393L563,393Z"/>
+</vector>
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index db0ea54..e60e0b0 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -33,7 +33,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
- android:elevation="@dimen/resolver_elevation"
android:paddingTop="24dp"
android:paddingStart="@dimen/resolver_edge_margin"
android:paddingEnd="@dimen/resolver_edge_margin"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b3d8f39..54dbc48 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1387,6 +1387,7 @@
<java-symbol type="drawable" name="platlogo" />
<java-symbol type="drawable" name="stat_notify_sync_error" />
<java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+ <java-symbol type="drawable" name="ic_thread_network" />
<java-symbol type="drawable" name="ic_wifi_signal_0" />
<java-symbol type="drawable" name="ic_wifi_signal_1" />
<java-symbol type="drawable" name="ic_wifi_signal_2" />
diff --git a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
index 4c00c16..9785ca7 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
@@ -216,6 +216,46 @@
}
@Test
+ public void testBufferSizeWithConsecutiveDelimiters() throws Exception {
+ // Read numbers using very small buffer size, exercising fillBuf()
+ // Include more consecutive delimiters than the buffer size.
+ final ProcFileReader reader =
+ buildReader("1 21 3 41 5 61 7 81 9 10\n", 3);
+
+ assertEquals(1, reader.nextInt());
+ assertEquals(21, reader.nextInt());
+ assertEquals(3, reader.nextInt());
+ assertEquals(41, reader.nextInt());
+ assertEquals(5, reader.nextInt());
+ assertEquals(61, reader.nextInt());
+ assertEquals(7, reader.nextInt());
+ assertEquals(81, reader.nextInt());
+ assertEquals(9, reader.nextInt());
+ assertEquals(10, reader.nextInt());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ @Test
+ public void testBufferSizeWithConsecutiveDelimitersAndMultipleLines() throws Exception {
+ final ProcFileReader reader =
+ buildReader("1 21 41 \n 5 7 81 \n 9 10 \n", 3);
+
+ assertEquals(1, reader.nextInt());
+ assertEquals(21, reader.nextInt());
+ assertEquals(41, reader.nextInt());
+ reader.finishLine();
+ assertEquals(5, reader.nextInt());
+ assertEquals(7, reader.nextInt());
+ assertEquals(81, reader.nextInt());
+ reader.finishLine();
+ assertEquals(9, reader.nextInt());
+ assertEquals(10, reader.nextInt());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ @Test
public void testIgnore() throws Exception {
final ProcFileReader reader = buildReader("a b c\n");
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 9d1e507..65615e6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -322,11 +322,11 @@
<library name="android.hidl.manager-V1.0-java"
file="/system/framework/android.hidl.manager-V1.0-java.jar" />
- <!-- These are the standard packages that are white-listed to always have internet
+ <!-- These are the standard packages that are allowed to always have internet
access while in power save mode, even if they aren't in the foreground. -->
<allow-in-power-save package="com.android.providers.downloads" />
- <!-- These are the standard packages that are white-listed to always have internet
+ <!-- These are the standard packages that are allowed to always have internet
access while in data mode, even if they aren't in the foreground. -->
<allow-in-data-usage-save package="com.android.providers.downloads" />
@@ -338,7 +338,7 @@
<!-- Emergency app needs to run in the background to reliably provide safety features -->
<allow-in-power-save package="com.android.emergency" />
- <!-- Whitelist system providers -->
+ <!-- Allow system providers -->
<!-- Calendar provider needs alarms while in idle -->
<allow-in-power-save package="com.android.providers.calendar" />
<allow-in-power-save-except-idle package="com.android.providers.contacts" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index e38038e..59092d4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -3137,11 +3137,9 @@
private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
@NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
final boolean isEmbedded = activityWindowInfo.isEmbedded();
- final Rect activityBounds = new Rect(activity.getResources().getConfiguration()
- .windowConfiguration.getBounds());
final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
- return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds,
+ return new EmbeddedActivityWindowInfo(activity, isEmbedded, taskBounds,
activityStackBounds);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 8bc3a30..a525877 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -1570,8 +1570,6 @@
mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
final boolean isEmbedded = true;
- final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration
- .getBounds();
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded();
@@ -1579,7 +1577,7 @@
doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds();
final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity,
- isEmbedded, activityBounds, taskBounds, activityStackBounds);
+ isEmbedded, taskBounds, activityStackBounds);
assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity));
}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 9f0a425..9599658 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -23,7 +23,8 @@
android:orientation="horizontal"
android:gravity="center"
android:padding="16dp"
- android:background="@drawable/desktop_mode_maximize_menu_background">
+ android:background="@drawable/desktop_mode_maximize_menu_background"
+ android:elevation="1dp">
<LinearLayout
android:layout_width="wrap_content"
@@ -37,7 +38,8 @@
android:background="@drawable/desktop_mode_maximize_menu_layout_background"
android:padding="4dp"
android:layout_marginRight="8dp"
- android:layout_marginBottom="4dp">
+ android:layout_marginBottom="4dp"
+ android:alpha="0">
<Button
android:id="@+id/maximize_menu_maximize_button"
style="?android:attr/buttonBarButtonStyle"
@@ -48,6 +50,7 @@
</FrameLayout>
<TextView
+ android:id="@+id/maximize_menu_maximize_window_text"
android:layout_width="94dp"
android:layout_height="18dp"
android:textSize="11sp"
@@ -55,7 +58,8 @@
android:gravity="center"
android:fontFamily="google-sans-text"
android:text="@string/desktop_mode_maximize_menu_maximize_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"/>
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
</LinearLayout>
<LinearLayout
@@ -69,7 +73,8 @@
android:orientation="horizontal"
android:padding="4dp"
android:background="@drawable/desktop_mode_maximize_menu_layout_background"
- android:layout_marginBottom="4dp">
+ android:layout_marginBottom="4dp"
+ android:alpha="0">
<Button
android:id="@+id/maximize_menu_snap_left_button"
style="?android:attr/buttonBarButtonStyle"
@@ -88,6 +93,7 @@
android:stateListAnimator="@null"/>
</LinearLayout>
<TextView
+ android:id="@+id/maximize_menu_snap_window_text"
android:layout_width="94dp"
android:layout_height="18dp"
android:textSize="11sp"
@@ -96,6 +102,8 @@
android:gravity="center"
android:fontFamily="google-sans-text"
android:text="@string/desktop_mode_maximize_menu_snap_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"/>
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
</LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
+
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c8bfe7a4..f532f96 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -464,6 +464,9 @@
<!-- The height of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_height">114dp</dimen>
+ <!-- The padding of the maximize menu in desktop mode. -->
+ <dimen name="desktop_mode_menu_padding">16dp</dimen>
+
<!-- The height of the buttons in the maximize menu. -->
<dimen name="desktop_mode_maximize_menu_button_height">52dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 899b7cc..22f0adc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -16,6 +16,9 @@
package com.android.wm.shell.windowdecor
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
import android.annotation.IdRes
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
@@ -30,16 +33,21 @@
import android.view.View.OnClickListener
import android.view.View.OnGenericMotionListener
import android.view.View.OnTouchListener
+import android.view.View.SCALE_Y
+import android.view.View.TRANSLATION_Y
+import android.view.View.TRANSLATION_Z
import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
+import android.widget.TextView
import android.window.TaskConstants
import androidx.core.content.withStyledAttributes
import com.android.internal.R.attr.colorAccentPrimary
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow
@@ -65,14 +73,13 @@
private var maximizeMenu: AdditionalWindow? = null
private lateinit var viewHost: SurfaceControlViewHost
private lateinit var leash: SurfaceControl
- private val shadowRadius = loadDimensionPixelSize(
- R.dimen.desktop_mode_maximize_menu_shadow_radius
- ).toFloat()
+ private val openMenuAnimatorSet = AnimatorSet()
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
+ private val menuPadding = loadDimensionPixelSize(R.dimen.desktop_mode_menu_padding)
private lateinit var snapRightButton: Button
private lateinit var snapLeftButton: Button
@@ -91,10 +98,12 @@
if (maximizeMenu != null) return
createMaximizeMenu()
setupMaximizeMenu()
+ animateOpenMenu()
}
/** Closes the maximize window and releases its view. */
fun close() {
+ openMenuAnimatorSet.cancel()
maximizeMenu?.releaseView()
maximizeMenu = null
}
@@ -134,8 +143,6 @@
// Bring menu to front when open
t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU)
.setPosition(leash, menuPosition.x, menuPosition.y)
- .setWindowCrop(leash, menuWidth, menuHeight)
- .setShadowRadius(leash, shadowRadius)
.setCornerRadius(leash, cornerRadius)
.show(leash)
maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier)
@@ -146,6 +153,77 @@
}
}
+ private fun animateOpenMenu() {
+ val viewHost = maximizeMenu?.mWindowViewHost
+ val maximizeMenuView = viewHost?.view ?: return
+ val maximizeWindowText = maximizeMenuView.requireViewById<TextView>(
+ R.id.maximize_menu_maximize_window_text)
+ val snapWindowText = maximizeMenuView.requireViewById<TextView>(
+ R.id.maximize_menu_snap_window_text)
+
+ openMenuAnimatorSet.playTogether(
+ ObjectAnimator.ofFloat(maximizeMenuView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f)
+ .apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ },
+ ValueAnimator.ofFloat(STARTING_MENU_HEIGHT_SCALE, 1f)
+ .apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ addUpdateListener {
+ // Animate padding so that controls stay pinned to the bottom of
+ // the menu.
+ val value = animatedValue as Float
+ val topPadding = menuPadding -
+ ((1 - value) * menuHeight).toInt()
+ maximizeMenuView.setPadding(menuPadding, topPadding,
+ menuPadding, menuPadding)
+ }
+ },
+ ValueAnimator.ofFloat(1 / STARTING_MENU_HEIGHT_SCALE, 1f).apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ addUpdateListener {
+ // Scale up the children of the maximize menu so that the menu
+ // scale is cancelled out and only the background is scaled.
+ val value = animatedValue as Float
+ maximizeButtonLayout.scaleY = value
+ snapButtonsLayout.scaleY = value
+ maximizeWindowText.scaleY = value
+ snapWindowText.scaleY = value
+ }
+ },
+ ObjectAnimator.ofFloat(maximizeMenuView, TRANSLATION_Y,
+ (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight, 0f).apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ },
+ ObjectAnimator.ofInt(maximizeMenuView.background, "alpha",
+ MAX_DRAWABLE_ALPHA_VALUE).apply {
+ duration = ALPHA_ANIMATION_DURATION_MS
+ },
+ ValueAnimator.ofFloat(0f, 1f)
+ .apply {
+ duration = ALPHA_ANIMATION_DURATION_MS
+ startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+ addUpdateListener {
+ val value = animatedValue as Float
+ maximizeButtonLayout.alpha = value
+ snapButtonsLayout.alpha = value
+ maximizeWindowText.alpha = value
+ snapWindowText.alpha = value
+ }
+ },
+ ObjectAnimator.ofFloat(maximizeMenuView, TRANSLATION_Z, MENU_Z_TRANSLATION)
+ .apply {
+ duration = ELEVATION_ANIMATION_DURATION_MS
+ startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+ }
+ )
+ openMenuAnimatorSet.start()
+ }
+
private fun loadDimensionPixelSize(resourceId: Int): Int {
return if (resourceId == Resources.ID_NULL) {
0
@@ -263,6 +341,14 @@
}
companion object {
+ // Open menu animation constants
+ private const val ALPHA_ANIMATION_DURATION_MS = 50L
+ private const val MAX_DRAWABLE_ALPHA_VALUE = 255
+ private const val STARTING_MENU_HEIGHT_SCALE = 0.8f
+ private const val MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+ private const val ELEVATION_ANIMATION_DURATION_MS = 50L
+ private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L
+ private const val MENU_Z_TRANSLATION = 1f
fun isMaximizeMenuView(@IdRes viewId: Int): Boolean {
return viewId == R.id.maximize_menu ||
viewId == R.id.maximize_menu_maximize_button ||
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 2aff2c3..3fc6154 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -46,6 +46,7 @@
android:paddingEnd="@dimen/autofill_view_right_padding"
android:paddingTop="@dimen/autofill_view_top_padding"
android:paddingBottom="@dimen/autofill_view_bottom_padding"
+ android:textDirection="locale"
android:orientation="vertical">
<TextView
@@ -53,6 +54,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textDirection="locale"
style="@style/autofill.TextTitle"/>
<TextView
@@ -60,6 +62,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:textDirection="locale"
style="@style/autofill.TextSubtitle"/>
</LinearLayout>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index c18d73c..d4f8f7d 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -94,7 +94,7 @@
android:name="keyboard_layout_swiss_german"
android:label="@string/keyboard_layout_swiss_german_label"
android:keyboardLayout="@raw/keyboard_layout_swiss_german"
- android:keyboardLocale="de-Latn-CH|gsw-Latn-CH"
+ android:keyboardLocale="de-Latn-CH,gsw-Latn-CH"
android:keyboardLayoutType="qwertz" />
<keyboard-layout
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
index 52c4893..d6704a5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
@@ -47,3 +47,7 @@
val ColorScheme.surfaceTone: Color
get() = primary.copy(SettingsOpacity.SurfaceTone)
+
+/** The overall background color in Settings. */
+val ColorScheme.settingsBackground: Color
+ get() = surfaceContainer
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
index 69aba71..0db01e8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -26,7 +26,6 @@
import androidx.compose.ui.platform.LocalContext
data class SettingsColorScheme(
- val background: Color = Color.Unspecified,
val categoryTitle: Color = Color.Unspecified,
val surface: Color = Color.Unspecified,
val surfaceHeader: Color = Color.Unspecified,
@@ -65,7 +64,6 @@
internal fun dynamicLightColorScheme(context: Context): SettingsColorScheme {
val tonalPalette = dynamicTonalPalette(context)
return SettingsColorScheme(
- background = tonalPalette.neutral95,
categoryTitle = tonalPalette.primary40,
surface = tonalPalette.neutral99,
surfaceHeader = tonalPalette.neutral90,
@@ -88,7 +86,6 @@
internal fun dynamicDarkColorScheme(context: Context): SettingsColorScheme {
val tonalPalette = dynamicTonalPalette(context)
return SettingsColorScheme(
- background = tonalPalette.neutral10,
categoryTitle = tonalPalette.primary90,
surface = tonalPalette.neutral20,
surfaceHeader = tonalPalette.neutral30,
@@ -102,7 +99,6 @@
internal fun darkColorScheme(): SettingsColorScheme {
val tonalPalette = tonalPalette()
return SettingsColorScheme(
- background = tonalPalette.neutral10,
categoryTitle = tonalPalette.primary90,
surface = tonalPalette.neutral20,
surfaceHeader = tonalPalette.neutral30,
@@ -116,7 +112,6 @@
internal fun lightColorScheme(): SettingsColorScheme {
val tonalPalette = tonalPalette()
return SettingsColorScheme(
- background = tonalPalette.neutral95,
categoryTitle = tonalPalette.primary40,
surface = tonalPalette.neutral99,
surfaceHeader = tonalPalette.neutral90,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index c395558..d14b960 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -29,12 +29,11 @@
@Composable
fun SettingsTheme(content: @Composable () -> Unit) {
val isDarkTheme = isSystemInDarkTheme()
- val settingsColorScheme = settingsColorScheme(isDarkTheme)
- val colorScheme = materialColorScheme(isDarkTheme).copy(
- background = settingsColorScheme.background,
- )
- MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
+ MaterialTheme(
+ colorScheme = materialColorScheme(isDarkTheme),
+ typography = rememberSettingsTypography(),
+ ) {
CompositionLocalProvider(
LocalColorScheme provides settingsColorScheme(isDarkTheme),
LocalContentColor provides MaterialTheme.colorScheme.onSurface,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 56534f4..706bd0a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -75,6 +75,7 @@
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.settingsBackground
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
@@ -140,7 +141,7 @@
@Composable
private fun topAppBarColors() = TopAppBarColors(
- containerColor = MaterialTheme.colorScheme.background,
+ containerColor = MaterialTheme.colorScheme.settingsBackground,
scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
titleContentColor = MaterialTheme.colorScheme.onSurface,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
index 711c8a7..feb9737b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
@@ -28,13 +28,14 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.settingsBackground
@Composable
fun HomeScaffold(title: String, content: @Composable () -> Unit) {
Column(
Modifier
.fillMaxSize()
- .background(color = MaterialTheme.colorScheme.background)
+ .background(color = MaterialTheme.colorScheme.settingsBackground)
.systemBarsPadding()
.verticalScroll(rememberScrollState()),
) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index c87178d..a49b358 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -59,6 +59,7 @@
import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.settingsBackground
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -90,6 +91,7 @@
onSearchQueryChange = { viewModel.searchQuery = it },
)
},
+ containerColor = MaterialTheme.colorScheme.settingsBackground,
) { paddingValues ->
Box(
Modifier
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 8919402..af7a146 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
@@ -36,6 +37,7 @@
import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.compose.verticalValues
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.settingsBackground
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -54,6 +56,7 @@
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
+ containerColor = MaterialTheme.colorScheme.settingsBackground,
) { paddingValues ->
Box(Modifier.padding(paddingValues.horizontalValues())) {
content(paddingValues.verticalValues())
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
index e39b175..fc40930 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -37,6 +37,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.settingsBackground
import com.android.settingslib.spa.framework.theme.toMediumWeight
data class BottomAppBarButton(
@@ -54,7 +55,7 @@
content: @Composable () -> Unit,
) {
ActivityTitle(title)
- Scaffold { innerPadding ->
+ Scaffold(containerColor = MaterialTheme.colorScheme.settingsBackground) { innerPadding ->
BoxWithConstraints(
Modifier
.padding(innerPadding)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt
index 625663d..f3f89e0 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt
@@ -31,23 +31,22 @@
@Test
fun testDynamicTheme() {
// The dynamic color could be different in different device, just check basic restrictions:
- // 1. text color is different with background color
+ // 1. text color is different with surface color
// 2. primary / spinner color is different with its on-item color
val ls = dynamicLightColorScheme(context)
- assertThat(ls.categoryTitle).isNotEqualTo(ls.background)
- assertThat(ls.secondaryText).isNotEqualTo(ls.background)
+ assertThat(ls.categoryTitle).isNotEqualTo(ls.surface)
+ assertThat(ls.secondaryText).isNotEqualTo(ls.surface)
assertThat(ls.primaryContainer).isNotEqualTo(ls.onPrimaryContainer)
val ds = dynamicDarkColorScheme(context)
- assertThat(ds.categoryTitle).isNotEqualTo(ds.background)
- assertThat(ds.secondaryText).isNotEqualTo(ds.background)
+ assertThat(ds.categoryTitle).isNotEqualTo(ds.surface)
+ assertThat(ds.secondaryText).isNotEqualTo(ds.surface)
assertThat(ds.primaryContainer).isNotEqualTo(ds.onPrimaryContainer)
}
@Test
fun testStaticTheme() {
val ls = lightColorScheme()
- assertThat(ls.background).isEqualTo(Color(red = 244, green = 239, blue = 244))
assertThat(ls.categoryTitle).isEqualTo(Color(red = 103, green = 80, blue = 164))
assertThat(ls.surface).isEqualTo(Color(red = 255, green = 251, blue = 254))
assertThat(ls.surfaceHeader).isEqualTo(Color(red = 230, green = 225, blue = 229))
@@ -56,7 +55,6 @@
assertThat(ls.onPrimaryContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
val ds = darkColorScheme()
- assertThat(ds.background).isEqualTo(Color(red = 28, green = 27, blue = 31))
assertThat(ds.categoryTitle).isEqualTo(Color(red = 234, green = 221, blue = 255))
assertThat(ds.surface).isEqualTo(Color(red = 49, green = 48, blue = 51))
assertThat(ds.surfaceHeader).isEqualTo(Color(red = 72, green = 70, blue = 73))
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
index bd8a54b..ed7735e 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
@@ -26,42 +26,35 @@
import androidx.compose.ui.text.font.FontFamily
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
-import org.mockito.kotlin.whenever
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class SettingsThemeTest {
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
@get:Rule
val composeTestRule = createComposeRule()
- @Mock
- private lateinit var context: Context
+ private val resources = mock<Resources> {
+ on { getString(any()) } doReturn ""
+ }
- @Mock
- private lateinit var resources: Resources
+ private val context = mock<Context> {
+ on { resources } doReturn resources
+ }
private var nextMockResId = 1
- @Before
- fun setUp() {
- whenever(context.resources).thenReturn(resources)
- whenever(resources.getString(any())).thenReturn("")
- }
-
private fun mockAndroidConfig(configName: String, configValue: String) {
- whenever(resources.getIdentifier(configName, "string", "android"))
- .thenReturn(nextMockResId)
- whenever(resources.getString(nextMockResId)).thenReturn(configValue)
+ resources.stub {
+ on { getIdentifier(configName, "string", "android") } doReturn nextMockResId
+ on { getString(nextMockResId) } doReturn configValue
+ }
nextMockResId++
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index c8f9135..991ce12 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -31,7 +31,6 @@
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
@@ -90,6 +89,7 @@
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
private static UiDevice sUiDevice;
+ private static String sLockSettings;
private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
@@ -108,6 +108,11 @@
sUiAutomation.adoptShellPermissionIdentity(
UiAutomation.ALL_PERMISSIONS.toArray(new String[0]));
sUiDevice = UiDevice.getInstance(sInstrumentation);
+ sLockSettings = sUiDevice.executeShellCommand("locksettings get-disabled");
+ Log.i(TAG, "locksettings get-disabled returns " + sLockSettings);
+ // Some test in the test class requires the device to be in lock screen
+ // ensure we have locksettings enabled before running the tests
+ sUiDevice.executeShellCommand("locksettings set-disabled false");
final Context context = sInstrumentation.getTargetContext();
sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -157,9 +162,10 @@
}
@AfterClass
- public static void classTeardown() {
+ public static void classTeardown() throws IOException {
Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
+ sUiDevice.executeShellCommand("locksettings set-disabled " + sLockSettings);
}
@Before
@@ -184,17 +190,17 @@
return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
}
- private static void wakeUpScreen() throws IOException {
+ private static void wakeUpScreen() {
sUiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP);
WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn);
- assertWithMessage("Screen is on").that(isScreenOn()).isTrue();
+ WaitUtils.ensureThat("Screen is on", AccessibilityMenuServiceTest::isScreenOn);
}
- private static void closeScreen() throws Throwable {
+ private static void closeScreen() {
// go/adb-cheats#lock-screen
sUiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP);
WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff);
- assertWithMessage("Screen is off").that(isScreenOff()).isTrue();
+ WaitUtils.ensureThat("Screen is off", AccessibilityMenuServiceTest::isScreenOff);
WaitUtils.ensureThat(
"Screen is locked", () -> sKeyguardManager.isKeyguardLocked());
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt
new file mode 100644
index 0000000..9b736b8
--- /dev/null
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene
+
+import com.android.systemui.notifications.ui.composable.NotificationsShadeScene
+import com.android.systemui.scene.shared.model.Scene
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface NotificationsShadeSceneModule {
+
+ @Binds @IntoSet fun notificationsShade(scene: NotificationsShadeScene): Scene
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d59f1f5..f73b6cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -28,6 +28,7 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -39,6 +40,10 @@
val Background = ElementKey("BouncerBackground")
val Content = ElementKey("BouncerContent")
}
+
+ object TestTags {
+ const val Root = "bouncer_root"
+ }
}
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
@@ -78,7 +83,9 @@
BouncerContent(
viewModel,
dialogFactory,
- Modifier.element(Bouncer.Elements.Content).fillMaxSize()
+ Modifier.sysuiResTag(Bouncer.TestTags.Root)
+ .element(Bouncer.Elements.Content)
+ .fillMaxSize()
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index c34f2fd..2dcd0ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -52,6 +52,7 @@
import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
/** UI for the input part of a password-requiring version of the bouncer. */
@@ -105,6 +106,7 @@
),
modifier =
modifier
+ .sysuiResTag("bouncer_text_entry")
.focusRequester(focusRequester)
.onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
.drawBehind {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 7af8408..d7e9c10 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -52,6 +52,7 @@
import com.android.internal.R
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
+import com.android.systemui.compose.modifiers.sysuiResTag
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
@@ -234,6 +235,7 @@
Canvas(
modifier
+ .sysuiResTag("bouncer_pattern_root")
// Because the width also includes spacing to the left and right of the leftmost and
// rightmost dots in the grid and because UX mocks specify the width without that
// spacing, the actual width needs to be defined slightly bigger than the UX mock width.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index a592aa9..79b57ca7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -469,6 +469,8 @@
size = size,
selected = selected && !isDragging,
widgetConfigurator = widgetConfigurator,
+ index = index,
+ contentListState = contentListState
)
}
} else {
@@ -478,6 +480,8 @@
viewModel = viewModel,
size = size,
selected = false,
+ index = index,
+ contentListState = contentListState
)
}
}
@@ -782,10 +786,21 @@
selected: Boolean,
modifier: Modifier = Modifier,
widgetConfigurator: WidgetConfigurator? = null,
+ index: Int,
+ contentListState: ContentListState,
) {
when (model) {
is CommunalContentModel.WidgetContent.Widget ->
- WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
+ WidgetContent(
+ viewModel,
+ model,
+ size,
+ selected,
+ widgetConfigurator,
+ modifier,
+ index,
+ contentListState
+ )
is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
is CommunalContentModel.WidgetContent.DisabledWidget ->
DisabledWidgetPlaceholder(model, viewModel, modifier)
@@ -883,6 +898,8 @@
selected: Boolean,
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
+ index: Int,
+ contentListState: ContentListState,
) {
val context = LocalContext.current
val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
@@ -891,6 +908,11 @@
model.providerInfo.loadLabel(context.packageManager).toString().trim()
}
val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget)
+ val removeWidgetActionLabel = stringResource(R.string.accessibility_action_label_remove_widget)
+ val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget)
+ val selectedKey by viewModel.selectedKey.collectAsState()
+ val selectedIndex =
+ selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
Box(
modifier =
modifier
@@ -907,6 +929,36 @@
Modifier.semantics {
contentDescription = accessibilityLabel
onClick(label = clickActionLabel, action = null)
+ val deleteAction =
+ CustomAccessibilityAction(removeWidgetActionLabel) {
+ contentListState.onRemove(index)
+ contentListState.onSaveList()
+ true
+ }
+ val selectWidgetAction =
+ CustomAccessibilityAction(clickActionLabel) {
+ val currentWidgetKey =
+ index?.let {
+ keyAtIndexIfEditable(contentListState.list, index)
+ }
+ viewModel.setSelectedKey(currentWidgetKey)
+ true
+ }
+
+ val actions = mutableListOf(deleteAction, selectWidgetAction)
+
+ if (selectedIndex != null && selectedIndex != index) {
+ actions.add(
+ CustomAccessibilityAction(placeWidgetActionLabel) {
+ contentListState.onMove(selectedIndex!!, index)
+ contentListState.onSaveList()
+ viewModel.setSelectedKey(null)
+ true
+ }
+ )
+ }
+
+ customActions = actions
}
}
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 722032c..63c70c9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -36,6 +36,7 @@
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.modifiers.thenIf
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
@@ -79,7 +80,7 @@
}
SceneTransitionLayout(
- modifier = modifier,
+ modifier = modifier.sysuiResTag("keyguard_clock_container"),
currentScene = currentScene,
onChangeScene = {},
transitions = ClockTransition.defaultClockTransitions,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
new file mode 100644
index 0000000..1f03408
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notifications.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.shade.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class NotificationsShadeScene
+@Inject
+constructor(
+ viewModel: NotificationsShadeSceneViewModel,
+ private val overlayShadeViewModel: OverlayShadeViewModel,
+) : ComposableScene {
+
+ override val key = Scenes.NotificationsShade
+
+ override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ viewModel.destinationScenes
+
+ @Composable
+ override fun SceneScope.Content(
+ modifier: Modifier,
+ ) {
+ OverlayShade(
+ viewModel = overlayShadeViewModel,
+ modifier = modifier,
+ horizontalArrangement = Arrangement.Start,
+ ) {
+ Text(
+ text = "Notifications list",
+ modifier = Modifier.padding(NotificationsShade.Dimensions.Padding)
+ )
+ }
+ }
+}
+
+object NotificationsShade {
+ object Dimensions {
+ val Padding = 16.dp
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index eedff89..5f84dd4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -17,6 +17,11 @@
package com.android.systemui.qs.footer.ui.compose
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
@@ -87,10 +92,24 @@
fun SceneScope.FooterActionsWithAnimatedVisibility(
viewModel: FooterActionsViewModel,
isCustomizing: Boolean,
+ customizingAnimationDuration: Int,
lifecycleOwner: LifecycleOwner,
modifier: Modifier = Modifier,
) {
- AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
+ AnimatedVisibility(
+ visible = !isCustomizing,
+ enter =
+ expandVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ initialHeight = { 0 },
+ ) + fadeIn(tween(customizingAnimationDuration)),
+ exit =
+ shrinkVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ targetHeight = { 0 },
+ ) + fadeOut(tween(customizingAnimationDuration)),
+ modifier = modifier.fillMaxWidth()
+ ) {
QuickSettingsTheme {
// This view has its own horizontal padding
// TODO(b/321716470) This should use a lifecycle tied to the scene.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index a87a8df..46be6b8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -162,7 +162,8 @@
modifier: Modifier = Modifier,
) {
val qsView by qsSceneAdapter.qsView.collectAsState(null)
- val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState()
+ val isCustomizing by
+ qsSceneAdapter.isCustomizerShowing.collectAsState(qsSceneAdapter.isCustomizerShowing.value)
QuickSettingsTheme {
val context = LocalContext.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index a32cc04..4c0f2e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -19,12 +19,15 @@
import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
@@ -178,6 +181,9 @@
}
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+ val customizingAnimationDuration by
+ viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
val screenHeight = LocalRawScreenHeight.current
BackHandler(
@@ -217,6 +223,18 @@
val navBarBottomHeight =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val density = LocalDensity.current
+ val bottomPadding by
+ animateDpAsState(
+ targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
+ animationSpec = tween(customizingAnimationDuration),
+ label = "animateQSSceneBottomPaddingAsState"
+ )
+ val topPadding by
+ animateDpAsState(
+ targetValue = if (isCustomizing) ShadeHeader.Dimensions.CollapsedHeight else 0.dp,
+ animationSpec = tween(customizingAnimationDuration),
+ label = "animateQSSceneTopPaddingAsState"
+ )
LaunchedEffect(navBarBottomHeight, density) {
with(density) {
@@ -236,17 +254,14 @@
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
Modifier.fillMaxSize()
- .then(
- if (isCustomizing) {
- Modifier.padding(top = 48.dp)
- } else {
- Modifier.padding(bottom = navBarBottomHeight)
- }
+ .padding(
+ top = topPadding.coerceAtLeast(0.dp),
+ bottom = bottomPadding.coerceAtLeast(0.dp)
)
) {
Box(modifier = Modifier.fillMaxSize().weight(1f)) {
val shadeHeaderAndQuickSettingsModifier =
- if (isCustomizing) {
+ if (isCustomizerShowing) {
Modifier.fillMaxHeight().align(Alignment.TopCenter)
} else {
Modifier.verticalNestedScrollToScene()
@@ -269,15 +284,22 @@
visible = !isCustomizing,
enter =
expandVertically(
- animationSpec = tween(100),
- initialHeight = { collapsedHeaderHeight },
- ) + fadeIn(tween(100)),
+ animationSpec = tween(customizingAnimationDuration),
+ expandFrom = Alignment.Top,
+ ) +
+ slideInVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ ) +
+ fadeIn(tween(customizingAnimationDuration)),
exit =
shrinkVertically(
- animationSpec = tween(100),
- targetHeight = { collapsedHeaderHeight },
+ animationSpec = tween(customizingAnimationDuration),
shrinkTowards = Alignment.Top,
- ) + fadeOut(tween(100)),
+ ) +
+ slideOutVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ ) +
+ fadeOut(tween(customizingAnimationDuration)),
) {
ExpandedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
@@ -303,7 +325,7 @@
viewModel.qsSceneAdapter,
{ viewModel.qsSceneAdapter.qsHeight },
isSplitShade = false,
- modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
+ modifier = Modifier.sysuiResTag("expanded_qs_scroll_view")
)
MediaCarousel(
@@ -318,6 +340,7 @@
FooterActionsWithAnimatedVisibility(
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
+ customizingAnimationDuration = customizingAnimationDuration,
lifecycleOwner = lifecycleOwner,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index d3b3d15..0bd38a1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -63,6 +63,7 @@
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -100,6 +101,10 @@
val ColorScheme.shadeHeaderText: Color
get() = Color.White
}
+
+ object TestTags {
+ const val Root = "shade_header_root"
+ }
}
@Composable
@@ -131,7 +136,7 @@
// This layout assumes it is globally positioned at (0, 0) and is the
// same size as the screen.
Layout(
- modifier = modifier,
+ modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root),
contents =
listOf(
{
@@ -261,7 +266,7 @@
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
- Box(modifier = modifier) {
+ Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
if (isPrivacyChipVisible) {
Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
PrivacyChip(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 72b8026..ef5d4e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -17,7 +17,9 @@
package com.android.systemui.shade.ui.composable
import android.view.ViewGroup
+import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
@@ -301,6 +303,9 @@
modifier: Modifier = Modifier,
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+ val customizingAnimationDuration by
+ viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
@@ -320,6 +325,12 @@
.collectAsState(0f)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
+ val bottomPadding by
+ animateDpAsState(
+ targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
+ animationSpec = tween(customizingAnimationDuration),
+ label = "animateQSSceneBottomPaddingAsState"
+ )
val density = LocalDensity.current
LaunchedEffect(navBarBottomHeight, density) {
with(density) {
@@ -390,16 +401,13 @@
)
Column(
verticalArrangement = Arrangement.Top,
- modifier =
- Modifier.fillMaxSize().thenIf(!isCustomizing) {
- Modifier.padding(bottom = navBarBottomHeight)
- },
+ modifier = Modifier.fillMaxSize().padding(bottom = bottomPadding),
) {
Column(
modifier =
Modifier.fillMaxSize()
.weight(1f)
- .thenIf(!isCustomizing) {
+ .thenIf(!isCustomizerShowing) {
Modifier.verticalNestedScrollToScene()
.verticalScroll(
quickSettingsScrollState,
@@ -432,6 +440,7 @@
FooterActionsWithAnimatedVisibility(
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
+ customizingAnimationDuration = customizingAnimationDuration,
lifecycleOwner = lifecycleOwner,
modifier =
Modifier.align(Alignment.CenterHorizontally)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index dfc04ff..456fb79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -57,6 +57,8 @@
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.activityStarter
@@ -1090,6 +1092,78 @@
.isEqualTo(USER_INFO_WORK.id)
}
+ @Test
+ fun showCommunalFromOccluded_enteredOccludedFromHub() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+
+ assertThat(showCommunalFromOccluded).isTrue()
+ }
+
+ @Test
+ fun showCommunalFromOccluded_enteredOccludedFromLockscreen() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+
+ assertThat(showCommunalFromOccluded).isFalse()
+ }
+
+ @Test
+ fun showCommunalFromOccluded_communalBecomesUnavailableWhileOccluded() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ runCurrent()
+ kosmos.setCommunalAvailable(false)
+
+ assertThat(showCommunalFromOccluded).isFalse()
+ }
+
+ @Test
+ fun showCommunalFromOccluded_showBouncerWhileOccluded() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ runCurrent()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ assertThat(showCommunalFromOccluded).isTrue()
+ }
+
private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
val timer = mock(SmartspaceTarget::class.java)
whenever(timer.smartspaceTargetId).thenReturn(id)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
index 7e0e7d1..302ac35 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
@@ -16,8 +16,8 @@
package com.android.systemui.qs.pipeline.domain.interactor
-import android.view.View
import com.android.internal.logging.InstanceId
+import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.qs.QSTile
class FakeQSTile(
@@ -56,11 +56,11 @@
callbacks.clear()
}
- override fun click(view: View?) {}
+ override fun click(expandable: Expandable?) {}
- override fun secondaryClick(view: View?) {}
+ override fun secondaryClick(expandable: Expandable?) {}
- override fun longClick(view: View?) {}
+ override fun longClick(expandable: Expandable?) {}
override fun userSwitch(currentUser: Int) {
user = currentUser
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
index 182a604..d309554 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+import android.content.Context
import android.provider.Settings
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -23,6 +24,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.animation.LaunchableView
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
@@ -36,7 +39,6 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth
import kotlinx.coroutines.test.runTest
@@ -63,6 +65,8 @@
@Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
@Mock private lateinit var dialog: SystemUIDialog
@Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var expandable: Expandable
+ @Mock private lateinit var controller: DialogTransitionAnimator.Controller
@Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
@@ -73,6 +77,9 @@
dialog = mock<SystemUIDialog>()
fontScalingDialogDelegate =
mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) }
+ controller = mock<DialogTransitionAnimator.Controller>()
+ expandable =
+ mock<Expandable> { whenever(dialogTransitionController(any())).thenReturn(controller) }
argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
underTest =
@@ -90,9 +97,8 @@
fun clickTile_screenUnlocked_showDialogAnimationFromView() =
kosmos.testScope.runTest {
keyguardStateController.isShowing = false
- val testView = View(context)
- underTest.handleInput(click(FontScalingTileModel, view = testView))
+ underTest.handleInput(click(FontScalingTileModel, expandable = expandable))
verify(activityStarter)
.executeRunnableDismissingKeyguard(
@@ -103,17 +109,15 @@
eq(false)
)
argumentCaptor.value.run()
- verify(mDialogTransitionAnimator)
- .showFromView(any(), eq(testView), nullable(), anyBoolean())
+ verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
}
@Test
fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() =
kosmos.testScope.runTest {
keyguardStateController.isShowing = true
- val testView = View(context)
- underTest.handleInput(click(FontScalingTileModel, view = testView))
+ underTest.handleInput(click(FontScalingTileModel, expandable = expandable))
verify(activityStarter)
.executeRunnableDismissingKeyguard(
@@ -124,8 +128,7 @@
eq(false)
)
argumentCaptor.value.run()
- verify(mDialogTransitionAnimator, never())
- .showFromView(any(), eq(testView), nullable(), anyBoolean())
+ verify(mDialogTransitionAnimator, never()).show(any(), any(), anyBoolean())
verify(dialog).show()
}
@@ -140,4 +143,8 @@
val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS
Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
}
+
+ private class FontScalingTileTestView(context: Context) : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
index b9321d5..91f4ea8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
@@ -18,12 +18,11 @@
import android.app.Dialog
import android.os.UserHandle
-import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -138,12 +137,17 @@
*/
@Test
fun handleClickFromView_whenDoingNothing_whenKeyguardNotShowing_showDialogFromView() = runTest {
- val view = mock<View>()
+ val expandable = mock<Expandable>()
+ val controller = mock<DialogTransitionAnimator.Controller>()
+ whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
+
kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
val recordingModel = ScreenRecordTileModel.DoingNothing
- underTest.handleInput(QSTileInputTestKtx.click(recordingModel, UserHandle.CURRENT, view))
+ underTest.handleInput(
+ QSTileInputTestKtx.click(recordingModel, UserHandle.CURRENT, expandable)
+ )
val onStartRecordingClickedCaptor = argumentCaptor<Runnable>()
verify(recordingController)
.createScreenRecordDialog(
@@ -158,6 +162,6 @@
verify(keyguardDismissUtil)
.executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true))
onDismissActionCaptor.value.onDismiss()
- verify(dialogTransitionAnimator).showFromView(eq(dialog), eq(view), any(), eq(true))
+ verify(dialogTransitionAnimator).show(eq(dialog), eq(controller), eq(true))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index f2eb7f4..c660ff3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -273,21 +273,56 @@
}
@Test
- fun customizing_QS() =
+ fun customizing_QS_noAnimations() =
testScope.runTest {
- val customizing by collectLastValue(underTest.isCustomizing)
+ val customizerState by collectLastValue(underTest.customizerState)
underTest.inflate(context)
runCurrent()
underTest.setState(QSSceneAdapter.State.QS)
- assertThat(customizing).isFalse()
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
underTest.setCustomizerShowing(true)
- assertThat(customizing).isTrue()
+ assertThat(customizerState).isEqualTo(CustomizerState.Showing)
underTest.setCustomizerShowing(false)
- assertThat(customizing).isFalse()
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
+ }
+
+ // This matches the calls made by QSCustomizer
+ @Test
+ fun customizing_QS_animations_correctStates() =
+ testScope.runTest {
+ val customizerState by collectLastValue(underTest.customizerState)
+ val animatingInDuration = 100L
+ val animatingOutDuration = 50L
+
+ underTest.inflate(context)
+ runCurrent()
+ underTest.setState(QSSceneAdapter.State.QS)
+
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
+
+ // Start showing customizer with animation
+ underTest.setCustomizerAnimating(true)
+ underTest.setCustomizerShowing(true, animatingInDuration)
+ assertThat(customizerState)
+ .isEqualTo(CustomizerState.AnimatingIntoCustomizer(animatingInDuration))
+
+ // Finish animation
+ underTest.setCustomizerAnimating(false)
+ assertThat(customizerState).isEqualTo(CustomizerState.Showing)
+
+ // Start closing customizer with animation
+ underTest.setCustomizerAnimating(true)
+ underTest.setCustomizerShowing(false, animatingOutDuration)
+ assertThat(customizerState)
+ .isEqualTo(CustomizerState.AnimatingOutOfCustomizer(animatingOutDuration))
+
+ // Finish animation
+ underTest.setCustomizerAnimating(false)
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 6e7e402..44c9695 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.shade.domain.startable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -37,6 +39,7 @@
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -49,7 +52,6 @@
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 platform.test.runner.parameterized.ParameterizedAndroidJunit4
@@ -67,7 +69,7 @@
private val fakeConfigurationRepository by lazy { kosmos.fakeConfigurationRepository }
private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
- private lateinit var underTest: ShadeStartable
+ private val underTest: ShadeStartable = kosmos.shadeStartable
companion object {
@JvmStatic
@@ -81,13 +83,9 @@
mSetFlagsRule.setFlagsParameterization(flags!!)
}
- @Before
- fun setup() {
- underTest = kosmos.shadeStartable
- }
-
@Test
- fun hydrateShadeMode() =
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun hydrateShadeMode_dualShadeDisabled() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
val shadeMode by collectLastValue(shadeInteractor.shadeMode)
@@ -105,6 +103,25 @@
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateShadeMode_dualShadeEnabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ val shadeMode by collectLastValue(shadeInteractor.shadeMode)
+
+ underTest.start()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ fakeConfigurationRepository.onAnyConfigurationChange()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ fakeConfigurationRepository.onAnyConfigurationChange()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+ }
+
+ @Test
@EnableSceneContainer
fun hydrateShadeExpansionStateManager() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
new file mode 100644
index 0000000..abc684c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Swipe
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class NotificationsShadeSceneViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+
+ private val underTest = kosmos.notificationsShadeSceneViewModel
+
+ @Test
+ fun upTransitionSceneKey_deviceLocked_lockscreen() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun upTransitionSceneKey_deviceUnlocked_gone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+ unlockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
+ sceneInteractor.changeScene(Scenes.Gone, "reason")
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone)
+ }
+
+ private fun TestScope.lockDevice() {
+ val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ runCurrent()
+ }
+
+ private fun TestScope.unlockDevice() {
+ val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ sceneInteractor.changeScene(Scenes.Gone, "reason")
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
index 55e46dc..e1be6b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -24,16 +24,17 @@
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.communal.util.fakeCommunalColors
import com.android.systemui.concurrency.fakeExecutor
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.wakefulnessLifecycle
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
@@ -63,7 +64,6 @@
import java.util.Optional
import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -186,29 +186,18 @@
verify(mRecentTasks).setTransitionBackgroundColor(null)
verify(mRecentTasks, never()).setTransitionBackgroundColor(black)
- setDocked(true)
- // Make communal available
- kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
- kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
- kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
-
+ // Transition to occluded from the glanceable hub.
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ kosmos.setCommunalAvailable(true)
runCurrent()
verify(mRecentTasks).setTransitionBackgroundColor(black)
}
- private fun TestScope.setDocked(docked: Boolean) {
- kosmos.fakeDockManager.setIsDocked(docked)
- val event =
- if (docked) {
- DockManager.STATE_DOCKED
- } else {
- DockManager.STATE_NONE
- }
- kosmos.fakeDockManager.setDockEvent(event)
- runCurrent()
- }
-
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index c9e2989..d13c750 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -21,11 +21,11 @@
import android.metrics.LogMaker;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
-import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.plugins.annotations.DependsOn;
import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.plugins.qs.QSTile.Callback;
@@ -58,23 +58,23 @@
/**
* The tile was clicked.
*
- * @param view The view that was clicked.
+ * @param expandable {@link Expandable} that was clicked.
*/
- void click(@Nullable View view);
+ void click(@Nullable Expandable expandable);
/**
* The tile secondary click was triggered.
*
- * @param view The view that was clicked.
+ * @param expandable {@link Expandable} that was clicked.
*/
- void secondaryClick(@Nullable View view);
+ void secondaryClick(@Nullable Expandable expandable);
/**
* The tile was long clicked.
*
- * @param view The view that was clicked.
+ * @param expandable {@link Expandable} that was clicked.
*/
- void longClick(@Nullable View view);
+ void longClick(@Nullable Expandable expandable);
void userSwitch(int currentUser);
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index dcdd4f0..45bcd82 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1186,6 +1186,10 @@
<string name="accessibility_content_description_for_communal_hub">Widgets on lock screen</string>
<!-- Label for accessibility action to select a widget in edit mode. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_select_widget">select widget</string>
+ <!-- Label for accessibility action to remove a widget in edit mode. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_remove_widget">remove widget</string>
+ <!-- Label for accessibility action to place a widget in edit mode after selecting move widget. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_place_widget">place selected widget</string>
<!-- Related to user switcher --><skip/>
diff --git a/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
index fe12134..863f0c3 100644
--- a/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
+++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
@@ -231,7 +231,7 @@
info.setClassName(EditText.class.getName());
info.setPassword(true);
info.setText(getTransformedText());
-
+ info.setSelected(false);
info.setEditable(true);
info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
index 623b40f..14e5f34 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
@@ -18,7 +18,6 @@
import android.bluetooth.BluetoothDevice;
import android.util.Log;
-import android.view.View;
import androidx.annotation.Nullable;
@@ -26,6 +25,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -58,9 +58,9 @@
/**
* Shows the dialog.
*
- * @param view The view from which the dialog is shown.
+ * @param expandable {@link Expandable} from which the dialog is shown.
*/
- public void showDialog(View view) {
+ public void showDialog(Expandable expandable) {
if (mDialog != null) {
if (DEBUG) {
Log.d(TAG, "HearingDevicesDialog already showing. Destroy it first.");
@@ -70,13 +70,17 @@
mDialog = mDialogFactory.create(!isAnyBondedHearingDevice()).createDialog();
- if (view != null) {
- mDialogTransitionAnimator.showFromView(mDialog, view,
+ if (expandable != null) {
+ DialogTransitionAnimator.Controller controller = expandable.dialogTransitionController(
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG), /* animateBackgroundBoundsChange= */ true);
- } else {
- mDialog.show();
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(mDialog,
+ controller, /* animateBackgroundBoundsChange= */ true);
+ return;
+ }
}
+ mDialog.show();
}
private void destroyDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index eb919e3..4369f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -32,6 +32,7 @@
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
@@ -82,7 +83,7 @@
* @param view The view from which the dialog is shown.
*/
@kotlinx.coroutines.ExperimentalCoroutinesApi
- fun showDialog(view: View?) {
+ fun showDialog(expandable: Expandable?) {
cancelJob()
job =
@@ -93,17 +94,15 @@
val dialog = dialogDelegate.createDialog()
val context = dialog.context
- view?.let {
- dialogTransitionAnimator.showFromView(
- dialog,
- it,
- animateBackgroundBoundsChange = true,
- cuj =
- DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
- )
+ val controller =
+ expandable?.dialogTransitionController(
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
)
+ controller?.let {
+ dialogTransitionAnimator.show(dialog, it, animateBackgroundBoundsChange = true)
}
?: dialog.show()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index 0534824..f1c3f94 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -323,6 +323,9 @@
alternateBouncerUIAvailable
.logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
.launchIn(applicationScope)
+ alternateBouncerVisible
+ .logDiffsForTable(buffer, "", "AlternateBouncerVisible", false)
+ .launchIn(applicationScope)
lastShownSecurityMode
.map { it.name }
.logDiffsForTable(buffer, "", "lastShownSecurityMode", null)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 5cf68b7..0042915 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -44,9 +44,10 @@
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -64,6 +65,7 @@
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
@@ -77,9 +79,11 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
@@ -92,6 +96,7 @@
@Inject
constructor(
@Application val applicationScope: CoroutineScope,
+ @Background val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
@@ -99,13 +104,13 @@
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
- private val communalSettingsInteractor: CommunalSettingsInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ communalSettingsInteractor: CommunalSettingsInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
- private val dockManager: DockManager,
sceneInteractor: SceneInteractor,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
@@ -145,8 +150,18 @@
replay = 1,
)
- /** Whether to show communal by default */
- val showByDefault: Flow<Boolean> = and(isCommunalAvailable, dockManager.retrieveIsDocked())
+ /** Whether to show communal when exiting the occluded state. */
+ val showCommunalFromOccluded: Flow<Boolean> =
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .filter { step -> step.to == KeyguardState.OCCLUDED }
+ .combine(isCommunalAvailable, ::Pair)
+ .map { (step, available) -> available && step.from == KeyguardState.GLANCEABLE_HUB }
+ .flowOn(bgDispatcher)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 1bee83b..337d873 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -63,8 +63,8 @@
)
.distinctUntilChanged()
- /** Whether to show communal by default */
- val showByDefault: Flow<Boolean> = communalInteractor.showByDefault
+ /** Whether to show communal when exiting the occluded state. */
+ val showCommunalFromOccluded: Flow<Boolean> = communalInteractor.showCommunalFromOccluded
val transitionFromOccludedEnded =
keyguardTransitionInteractor.transitionStepsFromState(KeyguardState.OCCLUDED).filter { step
@@ -74,8 +74,11 @@
}
val recentsBackgroundColor: Flow<Color?> =
- combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor ->
- if (showByDefault) {
+ combine(showCommunalFromOccluded, communalColors.backgroundColor) {
+ showCommunalFromOccluded,
+ backgroundColor,
+ ->
+ if (showCommunalFromOccluded) {
backgroundColor
} else {
null
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index c464ed1..4875f48 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -30,6 +30,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.filterNotNull
object QSLongPressEffectViewBinder {
@@ -49,63 +50,55 @@
launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) {
var effectAnimator: ValueAnimator? = null
- qsLongPressEffect.actionType.collect { action ->
- action?.let {
- when (it) {
- QSLongPressEffect.ActionType.CLICK -> {
- tile.performClick()
- qsLongPressEffect.clearActionType()
- }
- QSLongPressEffect.ActionType.LONG_PRESS -> {
- tile.prepareForLaunch()
- tile.performLongClick()
- qsLongPressEffect.clearActionType()
- }
- QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
- tile.resetLongPressEffectProperties()
- tile.performLongClick()
- qsLongPressEffect.clearActionType()
- }
- QSLongPressEffect.ActionType.START_ANIMATOR -> {
- if (effectAnimator?.isRunning != true) {
- effectAnimator =
- ValueAnimator.ofFloat(0f, 1f).apply {
- this.duration =
- qsLongPressEffect.effectDuration.toLong()
- interpolator = AccelerateDecelerateInterpolator()
+ qsLongPressEffect.actionType.filterNotNull().collect { action ->
+ when (action) {
+ QSLongPressEffect.ActionType.CLICK -> {
+ tile.performClick()
+ qsLongPressEffect.clearActionType()
+ }
+ QSLongPressEffect.ActionType.LONG_PRESS -> {
+ tile.prepareForLaunch()
+ tile.performLongClick()
+ qsLongPressEffect.clearActionType()
+ }
+ QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
+ tile.resetLongPressEffectProperties()
+ tile.performLongClick()
+ qsLongPressEffect.clearActionType()
+ }
+ QSLongPressEffect.ActionType.START_ANIMATOR -> {
+ if (effectAnimator?.isRunning != true) {
+ effectAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ this.duration =
+ qsLongPressEffect.effectDuration.toLong()
+ interpolator = AccelerateDecelerateInterpolator()
- doOnStart {
- qsLongPressEffect.handleAnimationStart()
+ doOnStart { qsLongPressEffect.handleAnimationStart() }
+ addUpdateListener {
+ val value = animatedValue as Float
+ if (value == 0f) {
+ tile.bringToFront()
+ } else {
+ tile.updateLongPressEffectProperties(value)
}
- addUpdateListener {
- val value = animatedValue as Float
- if (value == 0f) {
- tile.bringToFront()
- } else {
- tile.updateLongPressEffectProperties(value)
- }
- }
- doOnEnd {
- qsLongPressEffect.handleAnimationComplete()
- }
- doOnCancel {
- qsLongPressEffect.handleAnimationCancel()
- }
- start()
}
- }
+ doOnEnd { qsLongPressEffect.handleAnimationComplete() }
+ doOnCancel { qsLongPressEffect.handleAnimationCancel() }
+ start()
+ }
}
- QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> {
- effectAnimator?.let {
- val pausedProgress = it.animatedFraction
- qsLongPressEffect.playReverseHaptics(pausedProgress)
- it.reverse()
- }
+ }
+ QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> {
+ effectAnimator?.let {
+ val pausedProgress = it.animatedFraction
+ qsLongPressEffect.playReverseHaptics(pausedProgress)
+ it.reverse()
}
- QSLongPressEffect.ActionType.CANCEL_ANIMATOR -> {
- tile.resetLongPressEffectProperties()
- effectAnimator?.cancel()
- }
+ }
+ QSLongPressEffect.ActionType.CANCEL_ANIMATOR -> {
+ tile.resetLongPressEffectProperties()
+ effectAnimator?.cancel()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 00ec1a1..44e795c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -187,18 +187,15 @@
return;
}
- // current indication is updated to empty
+ // Current indication is updated to empty.
+ // Update to empty even if `currMsgShownForMinTime` is false.
if (mCurrIndicationType == type
&& !hasNewIndication
&& showAsap) {
- if (currMsgShownForMinTime) {
- if (mShowNextIndicationRunnable != null) {
- mShowNextIndicationRunnable.runImmediately();
- } else {
- showIndication(INDICATION_TYPE_NONE);
- }
+ if (mShowNextIndicationRunnable != null) {
+ mShowNextIndicationRunnable.runImmediately();
} else {
- scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch);
+ showIndication(INDICATION_TYPE_NONE);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6ef39f3..d6fd354 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -177,10 +177,6 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -190,6 +186,9 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -1234,7 +1233,7 @@
mUnoccludeAnimator.cancel();
}
- if (isDream || mShowCommunalByDefault) {
+ if (isDream || mShowCommunalWhenUnoccluding) {
initAlphaForAnimationTargets(wallpapers);
if (isDream) {
mDreamViewModel.get().startTransitionFromDream();
@@ -1372,7 +1371,7 @@
private final Lazy<DreamViewModel> mDreamViewModel;
private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private boolean mShowCommunalByDefault = false;
+ private boolean mShowCommunalWhenUnoccluding = false;
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -1630,8 +1629,10 @@
getRemoteSurfaceAlphaApplier());
mJavaAdapter.alwaysCollectFlow(dreamViewModel.getTransitionEnded(),
getFinishedCallbackConsumer());
- mJavaAdapter.alwaysCollectFlow(communalViewModel.getShowByDefault(),
- (showByDefault) -> mShowCommunalByDefault = showByDefault);
+ mJavaAdapter.alwaysCollectFlow(communalViewModel.getShowCommunalFromOccluded(),
+ (showCommunalFromOccluded) -> {
+ mShowCommunalWhenUnoccluding = showCommunalFromOccluded;
+ });
mJavaAdapter.alwaysCollectFlow(communalViewModel.getTransitionFromOccludedEnded(),
getFinishedCallbackConsumer());
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index a49b3ae..4a726ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -65,43 +65,23 @@
* @param blueprintId
* @return whether the transition has succeeded.
*/
- fun applyBlueprint(index: Int): Boolean {
- ArrayList(blueprintIdMap.values)[index]?.let {
- applyBlueprint(it)
- return true
- }
- return false
- }
-
- /**
- * Emits the blueprint value to the collectors.
- *
- * @param blueprintId
- * @return whether the transition has succeeded.
- */
fun applyBlueprint(blueprintId: String?): Boolean {
val blueprint = blueprintIdMap[blueprintId]
- return if (blueprint != null) {
- applyBlueprint(blueprint)
- true
- } else {
+ if (blueprint == null) {
Log.e(
TAG,
"Could not find blueprint with id: $blueprintId. " +
"Perhaps it was not added to KeyguardBlueprintModule?"
)
- false
+ return false
}
- }
- /** Emits the blueprint value to the collectors. */
- fun applyBlueprint(blueprint: KeyguardBlueprint?) {
if (blueprint == this.blueprint.value) {
- refreshBlueprint()
- return
+ return true
}
- blueprint?.let { this.blueprint.value = it }
+ this.blueprint.value = blueprint
+ return true
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index ee589f4..e51ba83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -87,12 +87,15 @@
scope.launch {
keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
.filterRelevantKeyguardStateAnd { onTop -> !onTop }
- .sample(communalInteractor.isIdleOnCommunal, communalInteractor.showByDefault)
- .collect { (_, isIdleOnCommunal, showCommunalByDefault) ->
+ .sample(
+ communalInteractor.isIdleOnCommunal,
+ communalInteractor.showCommunalFromOccluded,
+ )
+ .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal || showCommunalByDefault) {
+ if (isIdleOnCommunal || showCommunalFromOccluded) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
@@ -106,16 +109,16 @@
.sample(
keyguardInteractor.isKeyguardShowing,
communalInteractor.isIdleOnCommunal,
- communalInteractor.showByDefault,
+ communalInteractor.showCommunalFromOccluded,
)
.filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) ->
!isOccluded && isShowing
}
- .collect { (_, _, isIdleOnCommunal, showCommunalByDefault) ->
+ .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal || showCommunalByDefault) {
+ if (isIdleOnCommunal || showCommunalFromOccluded) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index da4f85e..cf995fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -82,12 +82,17 @@
}
/**
- * Transitions to a blueprint.
+ * Transitions to a blueprint, or refreshes it if already applied.
*
* @param blueprintId
* @return whether the transition has succeeded.
*/
- fun transitionToBlueprint(blueprintId: String): Boolean {
+ fun transitionOrRefreshBlueprint(blueprintId: String): Boolean {
+ if (blueprintId == blueprint.value.id) {
+ refreshBlueprint()
+ return true
+ }
+
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
@@ -97,7 +102,7 @@
* @param blueprintId
* @return whether the transition has succeeded.
*/
- fun transitionToBlueprint(blueprintId: Int): Boolean {
+ fun transitionToBlueprint(blueprintId: String): Boolean {
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 53f0132..18022a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.binder
import android.graphics.PixelFormat
+import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@@ -95,11 +96,11 @@
applicationScope.launch("$TAG#alternateBouncerWindowViewModel") {
alternateBouncerWindowViewModel.get().alternateBouncerWindowRequired.collect {
addAlternateBouncerWindowView ->
+ Log.d(TAG, "alternateBouncerWindowRequired=$addAlternateBouncerWindowView")
if (addAlternateBouncerWindowView) {
addViewToWindowManager()
- val scrim =
+ val scrim: ScrimView =
alternateBouncerView!!.requireViewById(R.id.alternate_bouncer_scrim)
- as ScrimView
scrim.viewAlpha = 0f
bind(alternateBouncerView!!, alternateBouncerDependencies.get())
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index b0d45ed..db47bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -132,8 +132,12 @@
view.getIconState(viewModel.type, viewModel.useAodVariant),
/* merge */ false
)
- fgIconView.contentDescription =
- fgIconView.resources.getString(viewModel.type.contentDescriptionResId)
+ if (viewModel.type.contentDescriptionResId != -1) {
+ fgIconView.contentDescription =
+ fgIconView.resources.getString(
+ viewModel.type.contentDescriptionResId
+ )
+ }
fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint)
fgIconView.setPadding(
viewModel.padding,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 2735aed..5713a15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -214,7 +214,7 @@
R.id.unlocked,
R.id.locked_aod,
context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable,
- /* reversible */ true,
+ /* reversible */ false,
)
}
@@ -252,6 +252,7 @@
IconType.LOCK -> lockIconState[0] = android.R.attr.state_first
IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last
IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle
+ IconType.NONE -> return StateSet.NOTHING
}
if (aod) {
lockIconState[1] = android.R.attr.state_single
@@ -265,6 +266,7 @@
LOCK(R.string.accessibility_lock_icon),
UNLOCK(R.string.accessibility_unlock_button),
FINGERPRINT(R.string.accessibility_fingerprint_label),
+ NONE(-1),
}
enum class AccessibilityHintType {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
index ce7ec0e..962cdf1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
@@ -46,15 +46,14 @@
return
}
- if (
- arg.isDigitsOnly() && keyguardBlueprintInteractor.transitionToBlueprint(arg.toInt())
- ) {
- pw.println("Transition succeeded!")
- } else if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
- pw.println("Transition succeeded!")
- } else {
- pw.println("Invalid argument! To see available blueprint ids, run:")
- pw.println("$ adb shell cmd statusbar blueprint help")
+ when {
+ arg.isDigitsOnly() -> pw.println("Invalid argument! Use string ids.")
+ keyguardBlueprintInteractor.transitionOrRefreshBlueprint(arg) ->
+ pw.println("Transition succeeded!")
+ else -> {
+ pw.println("Invalid argument! To see available blueprint ids, run:")
+ pw.println("$ adb shell cmd statusbar blueprint help")
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index e0a3af6..570f377 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -102,37 +102,40 @@
to = GONE,
)
- return shadeInteractor.isAnyExpanded.flatMapLatest { isAnyExpanded ->
- transitionAnimation
- .sharedFlow(
- duration = duration,
- interpolator = EMPHASIZED_ACCELERATE,
- onStart = {
- leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
- willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
- isShadeExpanded = isAnyExpanded
- },
- onStep = { 1f - it },
- )
- .map {
- if (willRunDismissFromKeyguard) {
- if (isShadeExpanded) {
+ return shadeInteractor.anyExpansion
+ .map { it > 0f }
+ .distinctUntilChanged()
+ .flatMapLatest { isAnyExpanded ->
+ transitionAnimation
+ .sharedFlow(
+ duration = duration,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
+ isShadeExpanded = isAnyExpanded
+ },
+ onStep = { 1f - it },
+ )
+ .map {
+ if (willRunDismissFromKeyguard) {
+ if (isShadeExpanded) {
+ ScrimAlpha(
+ behindAlpha = it,
+ notificationsAlpha = it,
+ )
+ } else {
+ ScrimAlpha()
+ }
+ } else if (leaveShadeOpen) {
ScrimAlpha(
- behindAlpha = it,
- notificationsAlpha = it,
+ behindAlpha = 1f,
+ notificationsAlpha = 1f,
)
} else {
- ScrimAlpha()
+ ScrimAlpha(behindAlpha = it)
}
- } else if (leaveShadeOpen) {
- ScrimAlpha(
- behindAlpha = 1f,
- notificationsAlpha = 1f,
- )
- } else {
- ScrimAlpha(behindAlpha = it)
}
- }
- }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index e26b75f..40be73e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -195,7 +195,14 @@
isUnlocked,
) { isListeningForUdfps, isUnlocked ->
if (isListeningForUdfps) {
- DeviceEntryIconView.IconType.FINGERPRINT
+ if (isUnlocked) {
+ // Don't show any UI until isUnlocked=false. This covers the case
+ // when the "Power button instantly locks > 0s" or the device doesn't lock
+ // immediately after a screen time.
+ DeviceEntryIconView.IconType.NONE
+ } else {
+ DeviceEntryIconView.IconType.FINGERPRINT
+ }
} else if (isUnlocked) {
DeviceEntryIconView.IconType.UNLOCK
} else {
@@ -211,7 +218,8 @@
when (deviceEntryStatus) {
DeviceEntryIconView.IconType.LOCK -> isUdfps
DeviceEntryIconView.IconType.UNLOCK -> true
- DeviceEntryIconView.IconType.FINGERPRINT -> false
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.NONE -> false
}
}
@@ -239,8 +247,8 @@
DeviceEntryIconView.IconType.LOCK ->
DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE
DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
- DeviceEntryIconView.IconType.FINGERPRINT ->
- DeviceEntryIconView.AccessibilityHintType.NONE
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index d4c8456e..2c1e75e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -88,12 +88,11 @@
isCommunalAvailable: Boolean,
shadeMode: ShadeMode,
): Map<UserAction, UserActionResult> {
+ val shadeSceneKey =
+ if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
+
val quickSettingsIfSingleShade =
- if (shadeMode is ShadeMode.Single) {
- Scenes.QuickSettings
- } else {
- Scenes.Shade
- }
+ if (shadeMode is ShadeMode.Single) Scenes.QuickSettings else shadeSceneKey
return mapOf(
Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
@@ -104,8 +103,8 @@
swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
// Swiping down, not from the edge, always navigates to the shade scene.
- swipeDown(pointerCount = 1) to Scenes.Shade,
- swipeDown(pointerCount = 2) to Scenes.Shade,
+ swipeDown(pointerCount = 1) to shadeSceneKey,
+ swipeDown(pointerCount = 2) to shadeSceneKey,
)
.filterValues { it != null }
.mapValues { checkNotNull(it.value) }
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index d995116..0c07c05 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -81,7 +81,10 @@
val EvaluatorByFlag =
mapOf<Int, (SceneContainerPluginState) -> Boolean>(
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone },
- SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it.scene == Scenes.Shade },
+ SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to
+ {
+ it.scene == Scenes.NotificationsShade || it.scene == Scenes.Shade
+ },
SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings },
SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer },
SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index d476e63..a14479b 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -67,6 +67,7 @@
import com.android.systemui.SystemUIApplication;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
@@ -323,7 +324,7 @@
// remaining estimate is disabled
if (!mCurrentBatterySnapshot.isHybrid() || mBucket < -1
|| mCurrentBatterySnapshot.getTimeRemainingMillis()
- < mCurrentBatterySnapshot.getSevereThresholdMillis()) {
+ < mCurrentBatterySnapshot.getSevereThresholdMillis()) {
nb.setColor(Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorError));
}
@@ -703,17 +704,23 @@
mSaverConfirmation = null;
logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_DISMISS);
});
- WeakReference<View> ref = mBatteryControllerLazy.get().getLastPowerSaverStartView();
- if (ref != null && ref.get() != null && ref.get().isAggregatedVisible()) {
- mDialogTransitionAnimator.showFromView(d, ref.get(),
+ WeakReference<Expandable> ref =
+ mBatteryControllerLazy.get().getLastPowerSaverStartExpandable();
+ if (ref != null && ref.get() != null) {
+ DialogTransitionAnimator.Controller controller = ref.get().dialogTransitionController(
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(d, controller);
+ } else {
+ d.show();
+ }
} else {
d.show();
}
logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_DIALOG);
mSaverConfirmation = d;
- mBatteryControllerLazy.get().clearLastPowerSaverStartView();
+ mBatteryControllerLazy.get().clearLastPowerSaverStartExpandable();
}
@VisibleForTesting
@@ -873,4 +880,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index b53c245..d26ae0a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -41,7 +41,6 @@
import android.text.format.DateUtils;
import android.util.Log;
import android.view.IWindowManager;
-import android.view.View;
import android.view.WindowManagerGlobal;
import android.widget.Switch;
@@ -52,6 +51,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -99,7 +99,7 @@
@Nullable
private CharSequence mDefaultLabel;
@Nullable
- private View mViewClicked;
+ private Expandable mExpandableClicked;
private final Context mUserContext;
@@ -347,7 +347,7 @@
mService.onStartListening();
}
} else {
- mViewClicked = null;
+ mExpandableClicked = null;
mService.onStopListening();
if (mIsTokenGranted && !mIsShowingDialog) {
try {
@@ -409,11 +409,11 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
return;
}
- mViewClicked = view;
+ mExpandableClicked = expandable;
try {
if (DEBUG) Log.d(TAG, "Adding token");
mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG,
@@ -541,11 +541,9 @@
Log.i(TAG, "The activity is starting");
ActivityTransitionAnimator.Controller controller =
- mViewClicked == null ? null :
- ActivityTransitionAnimator.Controller.fromView(
- mViewClicked,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
- );
+ mExpandableClicked == null ? null :
+ mExpandableClicked.activityTransitionController(
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
mActivityStarter.startPendingIntentMaybeDismissingKeyguard(
pendingIntent,
/* intentSentUiThreadCallback= */ null,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index 8fc66d3..a6cfa75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -16,8 +16,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
-import android.view.View
-import android.view.View.OnLongClickListener
+import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -26,8 +25,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
-class TileViewModel(private val tile: QSTile, val spec: TileSpec) :
- OnLongClickListener, View.OnClickListener {
+class TileViewModel(private val tile: QSTile, val spec: TileSpec) {
val state: Flow<QSTile.State> =
conflatedCallbackFlow {
val callback = QSTile.Callback { trySend(it.copy()) }
@@ -42,13 +40,12 @@
val currentState: QSTile.State
get() = tile.state
- override fun onClick(view: View?) {
- tile.click(view)
+ fun onClick(expandable: Expandable?) {
+ tile.click(expandable)
}
- override fun onLongClick(view: View?): Boolean {
- tile.longClick(view)
- return true
+ fun onLongClick(expandable: Expandable?) {
+ tile.longClick(expandable)
}
fun startListening(token: Any) = tile.setListening(token, true)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 1456747..c24113f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -42,7 +42,6 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
@@ -58,6 +57,7 @@
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
@@ -137,9 +137,9 @@
*
* Calls to the controller should be made here to set the new state of the device.
*
- * @param view The view that was clicked.
+ * @param expandable {@link Expandable} that was clicked.
*/
- protected abstract void handleClick(@Nullable View view);
+ protected abstract void handleClick(@Nullable Expandable expandable);
/**
* Update state of the tile based on device state
@@ -282,7 +282,8 @@
mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS);
}
- public void click(@Nullable View view) {
+ @Override
+ public void click(@Nullable Expandable expandable) {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)
.addTaggedData(FIELD_STATUS_BAR_STATE,
mStatusBarStateController.getState())));
@@ -292,11 +293,12 @@
mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
eventId);
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- mHandler.obtainMessage(H.CLICK, eventId, 0, view).sendToTarget();
+ mHandler.obtainMessage(H.CLICK, eventId, 0, expandable).sendToTarget();
}
}
- public void secondaryClick(@Nullable View view) {
+ @Override
+ public void secondaryClick(@Nullable Expandable expandable) {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)
.addTaggedData(FIELD_STATUS_BAR_STATE,
mStatusBarStateController.getState())));
@@ -305,11 +307,11 @@
final int eventId = mClickEventId++;
mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
mState.state, eventId);
- mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, view).sendToTarget();
+ mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, expandable).sendToTarget();
}
@Override
- public void longClick(@Nullable View view) {
+ public void longClick(@Nullable Expandable expandable) {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)
.addTaggedData(FIELD_STATUS_BAR_STATE,
mStatusBarStateController.getState())));
@@ -319,7 +321,7 @@
mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
eventId);
if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
- mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+ mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, expandable).sendToTarget();
}
}
@@ -397,22 +399,22 @@
*
* Defaults to {@link QSTileImpl#handleClick}
*
- * @param view The view that was clicked.
+ * @param expandable {@link Expandable} that was clicked.
*/
- protected void handleSecondaryClick(@Nullable View view) {
+ protected void handleSecondaryClick(@Nullable Expandable expandable) {
// Default to normal click.
- handleClick(view);
+ handleClick(expandable);
}
/**
* Handles long click on the tile by launching the {@link Intent} defined in
* {@link QSTileImpl#getLongClickIntent}.
*
- * @param view The view from which the opening window will be animated.
+ * @param expandable {@link Expandable} from which the opening window will be animated.
*/
- protected void handleLongClick(@Nullable View view) {
+ protected void handleLongClick(@Nullable Expandable expandable) {
ActivityTransitionAnimator.Controller animationController =
- view != null ? ActivityTransitionAnimator.Controller.fromView(view,
+ expandable != null ? expandable.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null;
mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
animationController);
@@ -591,16 +593,16 @@
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
- handleClick((View) msg.obj);
+ handleClick((Expandable) msg.obj);
}
} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";
mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);
- handleSecondaryClick((View) msg.obj);
+ handleSecondaryClick((Expandable) msg.obj);
} else if (msg.what == LONG_CLICK) {
name = "handleLongClick";
mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);
- handleLongClick((View) msg.obj);
+ handleLongClick((Expandable) msg.obj);
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
handleRefreshState(msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index ca5b771..f3852a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -53,6 +53,7 @@
import com.android.systemui.Flags
import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
import com.android.systemui.FontSizeUtils
+import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
import com.android.systemui.haptics.qs.QSLongPressEffect
@@ -364,10 +365,11 @@
}
override fun init(tile: QSTile) {
+ val expandable = Expandable.fromView(this)
init(
- { v: View? -> tile.click(this) },
- { view: View? ->
- tile.longClick(this)
+ { _: View? -> tile.click(expandable) },
+ { _: View? ->
+ tile.longClick(expandable)
true
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 17251c3..2068799 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -29,13 +29,13 @@
import android.service.quicksettings.Tile;
import android.sysprop.TelephonyProperties;
import android.telephony.TelephonyManager;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -103,7 +103,7 @@
}
@Override
- public void handleClick(@Nullable View view) {
+ public void handleClick(@Nullable Expandable expandable) {
boolean airplaneModeEnabled = mState.value;
MetricsLogger.action(mContext, getMetricsCategory(), !airplaneModeEnabled);
if (!airplaneModeEnabled && TelephonyProperties.in_ecm_mode().orElse(false)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 688f3ca..73d991f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -9,12 +9,10 @@
import android.service.quicksettings.Tile
import android.text.TextUtils
import android.text.format.DateFormat
-import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -25,12 +23,15 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.NextAlarmController
import java.util.Locale
import javax.inject.Inject
-class AlarmTile @Inject constructor(
+class AlarmTile
+@Inject
+constructor(
host: QSHost,
uiEventLogger: QsEventLogger,
@Background backgroundLooper: Looper,
@@ -56,8 +57,7 @@
private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
private val icon = ResourceIcon.get(R.drawable.ic_alarm)
- @VisibleForTesting
- internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
+ @VisibleForTesting internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
lastAlarmInfo = nextAlarm
refreshState()
@@ -73,11 +73,11 @@
}
}
- override fun handleClick(view: View?) {
- val animationController = view?.let {
- ActivityTransitionAnimator.Controller.fromView(
- it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
- }
+ override fun handleClick(expandable: Expandable?) {
+ val animationController =
+ expandable?.activityTransitionController(
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ )
val pendingIntent = lastAlarmInfo?.showIntent
if (pendingIntent != null) {
mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 426aa55..7c0ce4c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -21,7 +21,6 @@
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -29,6 +28,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -121,7 +121,7 @@
if (!listening) {
// If we stopped listening, it means that the tile is not visible. In that case, we
// don't need to save the view anymore
- mBatteryController.clearLastPowerSaverStartView();
+ mBatteryController.clearLastPowerSaverStartExpandable();
}
}
@@ -131,11 +131,11 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (getState().state == Tile.STATE_UNAVAILABLE) {
return;
}
- mBatteryController.setPowerSaveMode(!mPowerSave, view);
+ mBatteryController.setPowerSaveMode(!mPowerSave, expandable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 6eae32a..9af34f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -31,7 +31,6 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
import android.widget.Switch;
import com.android.internal.logging.MetricsLogger;
@@ -39,6 +38,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -109,9 +109,9 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
- mDialogViewModel.showDialog(view);
+ mDialogViewModel.showDialog(expandable);
} else {
// Secondary clicks are header clicks, just toggle.
final boolean isEnabled = mState.value;
@@ -127,7 +127,7 @@
}
@Override
- protected void handleSecondaryClick(@Nullable View view) {
+ protected void handleSecondaryClick(@Nullable Expandable expandable) {
if (!mController.canConfigBluetooth()) {
mActivityStarter.postStartActivityDismissingKeyguard(
new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index b27b974..169cdc1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -29,7 +29,6 @@
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.util.Log;
-import android.view.View;
import android.widget.Button;
import androidx.annotation.Nullable;
@@ -41,6 +40,7 @@
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -161,12 +161,12 @@
}
@Override
- protected void handleLongClick(@Nullable View view) {
- handleClick(view);
+ protected void handleLongClick(@Nullable Expandable expandable) {
+ handleClick(expandable);
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (getState().state == Tile.STATE_UNAVAILABLE) {
return;
}
@@ -174,7 +174,7 @@
List<CastDevice> activeDevices = getActiveDevices();
if (willPopDialog()) {
if (!mKeyguard.isShowing()) {
- showDialog(view);
+ showDialog(expandable);
} else {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
// Dismissing the keyguard will collapse the shade, so we don't animate from the
@@ -216,7 +216,7 @@
}
}
- private void showDialog(@Nullable View view) {
+ private void showDialog(@Nullable Expandable expandable) {
mUiHandler.post(() -> {
final DialogHolder holder = new DialogHolder();
final Dialog dialog = MediaRouteDialogPresenter.createDialog(
@@ -241,17 +241,21 @@
SystemUIDialog.setDialogSize(dialog);
mUiHandler.post(() -> {
- if (view != null) {
- mDialogTransitionAnimator.showFromView(dialog, view,
- new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
- } else {
- if (dialog.getWindow() != null) {
- DialogKt.registerAnimationOnBackInvoked(dialog,
- dialog.getWindow().getDecorView());
+ if (expandable != null) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(
+ new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog, controller);
+ return;
}
- dialog.show();
}
+ if (dialog.getWindow() != null) {
+ DialogKt.registerAnimationOnBackInvoked(dialog,
+ dialog.getWindow().getDecorView());
+ }
+ dialog.show();
});
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index c8adbfc..871973df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -22,12 +22,12 @@
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -109,7 +109,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
mSetting.setValue(mState.value ? 0 : 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index c34a584..5896910 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -22,13 +22,13 @@
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -108,7 +108,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
mSetting.setValue(mState.value ? 0 : 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 58630a0..7760943 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -19,7 +19,6 @@
import android.os.Looper;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -30,6 +29,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -47,7 +47,7 @@
import javax.inject.Inject;
public class DataSaverTile extends QSTileImpl<BooleanState> implements
- DataSaverController.Listener{
+ DataSaverController.Listener {
public static final String TILE_SPEC = "saver";
@@ -89,8 +89,9 @@
public Intent getLongClickIntent() {
return new Intent(Settings.ACTION_DATA_SAVER_SETTINGS);
}
+
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (mState.value
|| Prefs.getBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, false)) {
// Do it right away.
@@ -112,10 +113,16 @@
dialog.setNeutralButton(com.android.internal.R.string.cancel, null);
dialog.setShowForAllUsers(true);
- if (view != null) {
- mDialogTransitionAnimator.showFromView(dialog, view, new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
+ if (expandable != null) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog, controller);
+ } else {
+ dialog.show();
+ }
} else {
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index bb175e2..cc8a734 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2021 The Android Open Source Project
*
@@ -21,12 +22,11 @@
import android.os.Handler
import android.os.Looper
import android.service.quicksettings.Tile
-import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
@@ -100,26 +100,30 @@
}
}
- override fun handleClick(view: View?) {
+ override fun handleClick(expandable: Expandable?) {
if (state.state == Tile.STATE_UNAVAILABLE) {
return
}
val intent = Intent().apply {
component = ComponentName(mContext, controlsComponent.getControlsUiController().get()
- .resolveActivity())
+ .resolveActivity())
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(ControlsUiController.EXTRA_ANIMATE, true)
}
- val animationController = view?.let {
- ActivityTransitionAnimator.Controller.fromView(
- it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
- }
+ val animationController =
+ expandable?.activityTransitionController(
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ )
mUiHandler.post {
val showOverLockscreenWhenLocked = state.state == Tile.STATE_ACTIVE
mActivityStarter.startActivity(
- intent, true /* dismissShade */, animationController, showOverLockscreenWhenLocked)
+ intent,
+ true /* dismissShade */,
+ animationController,
+ showOverLockscreenWhenLocked,
+ )
}
}
@@ -130,7 +134,7 @@
if (controlsComponent.isEnabled() && hasControlsApps.get()) {
if (controlsComponent.getVisibility() == AVAILABLE) {
val selection = controlsComponent
- .getControlsController().get().getPreferredSelection()
+ .getControlsController().get().getPreferredSelection()
state.state = if (selection is SelectedItem.StructureItem &&
selection.structure.controls.isEmpty()) {
Tile.STATE_INACTIVE
@@ -157,7 +161,7 @@
return null
}
- override fun handleLongClick(view: View?) {}
+ override fun handleLongClick(expandable: Expandable?) {}
override fun getTileLabel(): CharSequence {
return mContext.getText(controlsComponent.getTileTitleId())
@@ -166,4 +170,4 @@
companion object {
const val TILE_SPEC = "controls"
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f62b60b..4ebebea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -35,7 +35,6 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -47,6 +46,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -147,12 +147,12 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
// Zen is currently on
if (mState.value) {
mController.setZen(ZEN_MODE_OFF, null, TAG);
} else {
- enableZenMode(view);
+ enableZenMode(expandable);
}
}
@@ -162,7 +162,7 @@
mSettingZenDuration.setUserId(newUserId);
}
- private void enableZenMode(@Nullable View view) {
+ private void enableZenMode(@Nullable Expandable expandable) {
int zenDuration = mSettingZenDuration.getValue();
boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
@@ -183,11 +183,17 @@
case Settings.Secure.ZEN_DURATION_PROMPT:
mUiHandler.post(() -> {
Dialog dialog = makeZenModeDialog();
- if (view != null) {
- mDialogTransitionAnimator.showFromView(dialog, view, new DialogCuj(
+ if (expandable != null) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(new DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG),
- /* animateBackgroundBoundsChange= */ false);
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog,
+ controller, /* animateBackgroundBoundsChange= */ false);
+ } else {
+ dialog.show();
+ }
} else {
dialog.show();
}
@@ -217,8 +223,8 @@
}
@Override
- protected void handleSecondaryClick(@Nullable View view) {
- handleLongClick(view);
+ protected void handleSecondaryClick(@Nullable Expandable expandable) {
+ handleLongClick(expandable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index 4f0a63b..0d3d980 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -32,12 +32,12 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -153,7 +153,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
try {
if (mDreamManager.isDreaming()) {
mDreamManager.awaken();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index f022981..848ff3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -22,13 +22,13 @@
import android.os.Looper;
import android.provider.MediaStore;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -99,7 +99,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (ActivityManager.isUserAMonkey()) {
return;
}
@@ -114,8 +114,8 @@
}
@Override
- protected void handleLongClick(@Nullable View view) {
- handleClick(view);
+ protected void handleLongClick(@Nullable Expandable expandable) {
+ handleClick(expandable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index f5018a2..078698c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -19,12 +19,12 @@
import android.os.Handler
import android.os.Looper
import android.provider.Settings
-import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -74,18 +74,23 @@
return QSTile.State()
}
- override fun handleClick(view: View?) {
+ override fun handleClick(expandable: Expandable?) {
// We animate from the touched view only if we are not on the keyguard
- val animateFromView: Boolean = view != null && !keyguardStateController.isShowing
+ val animateFromExpandable: Boolean =
+ expandable != null && !keyguardStateController.isShowing
val runnable = Runnable {
val dialog: SystemUIDialog = fontScalingDialogDelegateProvider.get().createDialog()
- if (animateFromView) {
- dialogTransitionAnimator.showFromView(
- dialog,
- view!!,
- DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
- )
+ if (animateFromExpandable) {
+ val controller =
+ expandable?.dialogTransitionController(
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ controller?.let { dialogTransitionAnimator.show(dialog, controller) }
+ ?: dialog.show()
} else {
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
index 81a2026..183c1a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -20,13 +20,13 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
-import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Flags;
import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -72,8 +72,8 @@
}
@Override
- protected void handleClick(@Nullable View view) {
- mUiHandler.post(() -> mDialogManager.showDialog(view));
+ protected void handleClick(@Nullable Expandable expandable) {
+ mUiHandler.post(() -> mDialogManager.showDialog(expandable));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 4d0404d..ea3993e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -25,7 +25,6 @@
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.util.Log;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -33,6 +32,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -112,7 +112,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
final boolean isEnabled = mState.value;
if (!isEnabled && mDataSaverController.isDataSaverEnabled()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 0f260e3..6d98da4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -30,7 +30,6 @@
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -41,6 +40,7 @@
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -124,10 +124,10 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
mHandler.post(() -> mInternetDialogManager.create(true,
mAccessPointController.canConfigMobileData(),
- mAccessPointController.canConfigWifi(), view));
+ mAccessPointController.canConfigWifi(), expandable));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 357743b..932dec5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -20,9 +20,9 @@
import android.os.Handler
import android.os.Looper
import android.provider.Settings
-import android.view.View
import android.widget.Switch
import com.android.internal.logging.MetricsLogger
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -44,18 +44,18 @@
class InternetTileNewImpl
@Inject
constructor(
- host: QSHost,
- uiEventLogger: QsEventLogger,
- @Background backgroundLooper: Looper,
- @Main private val mainHandler: Handler,
- falsingManager: FalsingManager,
- metricsLogger: MetricsLogger,
- statusBarStateController: StatusBarStateController,
- activityStarter: ActivityStarter,
- qsLogger: QSLogger,
- viewModel: InternetTileViewModel,
- private val internetDialogManager: InternetDialogManager,
- private val accessPointController: AccessPointController,
+ host: QSHost,
+ uiEventLogger: QsEventLogger,
+ @Background backgroundLooper: Looper,
+ @Main private val mainHandler: Handler,
+ falsingManager: FalsingManager,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger,
+ viewModel: InternetTileViewModel,
+ private val internetDialogManager: InternetDialogManager,
+ private val accessPointController: AccessPointController,
) :
QSTileImpl<QSTile.BooleanState>(
host,
@@ -84,13 +84,13 @@
return QSTile.BooleanState().also { it.forceExpandIcon = true }
}
- override fun handleClick(view: View?) {
+ override fun handleClick(expandable: Expandable?) {
mainHandler.post {
internetDialogManager.create(
aboveStatusBar = true,
accessPointController.canConfigMobileData(),
accessPointController.canConfigWifi(),
- view,
+ expandable,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index b3f0d8b..cad5c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -22,13 +22,13 @@
import android.os.UserManager;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -92,7 +92,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
final boolean wasEnabled = mState.value;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index d650f73..136eea8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -27,13 +27,13 @@
import android.os.Looper;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -119,7 +119,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (getAdapter() == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index a1ea46d..ac762de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -28,7 +28,6 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -36,6 +35,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.NightDisplayListenerModule;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -112,7 +112,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
// Enroll in forced auto mode if eligible.
if ("1".equals(Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE))
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index b08e6a5..450c954 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -21,13 +21,13 @@
import android.os.Looper;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -114,7 +114,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
mSetting.setValue(mState.value ? 0 : 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index de9a08e..9766fac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -21,13 +21,13 @@
import android.os.Looper;
import android.service.quicksettings.Tile;
import android.util.Log;
-import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -99,7 +99,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
Intent intent = mQRCodeScannerController.getIntent();
if (intent == null) {
// This should never happen as the fact that we are handling clicks means that the
@@ -109,7 +109,7 @@
}
ActivityTransitionAnimator.Controller animationController =
- view == null ? null : ActivityTransitionAnimator.Controller.fromView(view,
+ expandable == null ? null : expandable.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
mActivityStarter.startActivity(intent, true /* dismissShade */,
animationController, true /* showOverLockscreenWhenLocked */);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index e1b742e..76aa146 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -35,7 +35,6 @@
import android.service.quickaccesswallet.WalletCard;
import android.service.quicksettings.Tile;
import android.util.Log;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -44,6 +43,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -131,9 +131,9 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
ActivityTransitionAnimator.Controller animationController =
- view == null ? null : ActivityTransitionAnimator.Controller.fromView(view,
+ expandable == null ? null : expandable.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
mUiHandler.post(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index b418a17..9937ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -24,7 +24,6 @@
import android.os.Looper
import android.service.quicksettings.Tile
import android.text.TextUtils
-import android.view.View
import android.widget.Switch
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN
@@ -32,6 +31,7 @@
import com.android.systemui.Flags.recordIssueQsTile
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -113,11 +113,11 @@
}
@VisibleForTesting
- public override fun handleClick(view: View?) {
+ public override fun handleClick(expandable: Expandable?) {
if (issueRecordingState.isRecording) {
stopIssueRecordingService()
} else {
- mUiHandler.post { showPrompt(view) }
+ mUiHandler.post { showPrompt(expandable) }
}
}
@@ -143,7 +143,7 @@
)
.send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
- private fun showPrompt(view: View?) {
+ private fun showPrompt(expandable: Expandable?) {
val dialog: AlertDialog =
delegateFactory
.create {
@@ -156,12 +156,11 @@
ActivityStarter.OnDismissAction {
// We animate from the touched view only if we are not on the keyguard, given
// that if we are we will dismiss it which will also collapse the shade.
- if (view != null && !keyguardStateController.isShowing) {
- dialogTransitionAnimator.showFromView(
- dialog,
- view,
- DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC)
- )
+ if (expandable != null && !keyguardStateController.isShowing) {
+ expandable
+ .dialogTransitionController(DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC))
+ ?.let { dialogTransitionAnimator.show(dialog, it) }
+ ?: dialog.show()
} else {
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 76ada10..3472352 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -23,13 +23,13 @@
import android.os.Looper;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -97,7 +97,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index f1d8f9f..35e43b6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -29,13 +29,13 @@
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -131,7 +131,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
final boolean newState = !mState.value;
mController.setRotationLocked(!newState, /* caller= */ "RotationLockTile#handleClick");
refreshState(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 1a90d43..4715230 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -23,7 +23,6 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -32,6 +31,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -118,13 +118,13 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (mController.isStarting()) {
cancelCountdown();
} else if (mController.isRecording()) {
stopRecording();
} else {
- mUiHandler.post(() -> showPrompt(view));
+ mUiHandler.post(() -> showPrompt(expandable));
}
refreshState();
}
@@ -174,10 +174,11 @@
return mContext.getString(R.string.quick_settings_screen_record_label);
}
- private void showPrompt(@Nullable View view) {
+ private void showPrompt(@Nullable Expandable expandable) {
// We animate from the touched view only if we are not on the keyguard, given that if we
// are we will dismiss it which will also collapse the shade.
- boolean shouldAnimateFromView = view != null && !mKeyguardStateController.isShowing();
+ boolean shouldAnimateFromExpandable =
+ expandable != null && !mKeyguardStateController.isShowing();
// Create the recording dialog that will collapse the shade only if we start the recording.
Runnable onStartRecordingClicked = () -> {
@@ -192,10 +193,17 @@
mDialogTransitionAnimator, mActivityStarter, onStartRecordingClicked);
ActivityStarter.OnDismissAction dismissAction = () -> {
- if (shouldAnimateFromView) {
- mDialogTransitionAnimator.showFromView(dialog, view, new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG),
- /* animateBackgroundBoundsChange= */ true);
+ if (shouldAnimateFromExpandable) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog,
+ controller, /* animateBackgroundBoundsChange= */ true);
+ } else {
+ dialog.show();
+ }
} else {
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 3eeb2a3..036ce08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -26,13 +26,13 @@
import android.safetycenter.SafetyCenterManager;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -100,7 +100,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
boolean blocked = mSensorPrivacyController.isSensorBlocked(getSensorId());
if (mSensorPrivacyController.requiresAuthentication()
&& mKeyguard.isMethodSecure()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index d92873ada..bec6581 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -24,13 +24,13 @@
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -107,7 +107,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
if (getState().state == Tile.STATE_UNAVAILABLE) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index abc4812..d9546ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -24,7 +24,6 @@
import android.os.Looper;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.view.View;
import android.widget.Switch;
import androidx.annotation.MainThread;
@@ -32,6 +31,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -88,7 +88,7 @@
}
@Override
- public void handleClick(@Nullable View view) {
+ public void handleClick(@Nullable Expandable expandable) {
mProfileController.setWorkModeEnabled(!mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 7192f58..2d3120a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -20,9 +20,9 @@
import android.content.Intent
import android.content.pm.PackageManager
import android.os.UserHandle
-import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
@@ -33,11 +33,11 @@
*/
interface QSTileIntentUserInputHandler {
- fun handle(view: View?, intent: Intent)
+ fun handle(expandable: Expandable?, intent: Intent)
/** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
fun handle(
- view: View?,
+ expandable: Expandable?,
pendingIntent: PendingIntent,
requestLaunchingDefaultActivity: Boolean = false
)
@@ -52,31 +52,25 @@
private val userHandle: UserHandle,
) : QSTileIntentUserInputHandler {
- override fun handle(view: View?, intent: Intent) {
+ override fun handle(expandable: Expandable?, intent: Intent) {
val animationController: ActivityTransitionAnimator.Controller? =
- view?.let {
- ActivityTransitionAnimator.Controller.fromView(
- it,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
- )
- }
+ expandable?.activityTransitionController(
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ )
activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
}
// TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
override fun handle(
- view: View?,
+ expandable: Expandable?,
pendingIntent: PendingIntent,
requestLaunchingDefaultActivity: Boolean
) {
if (pendingIntent.isActivity) {
val animationController: ActivityTransitionAnimator.Controller? =
- view?.let {
- ActivityTransitionAnimator.Controller.fromView(
- it,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
- )
- }
+ expandable?.activityTransitionController(
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ )
activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
} else if (requestLaunchingDefaultActivity) {
val intent =
@@ -97,7 +91,7 @@
?.let { resolved ->
intent.setPackage(null)
intent.setComponent(resolved.activityInfo.componentName)
- handle(view, intent)
+ handle(expandable, intent)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
index 5aef950..246fe38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
@@ -16,10 +16,10 @@
package com.android.systemui.qs.tiles.dialog
import android.util.Log
-import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -47,14 +47,14 @@
}
/**
- * Creates a [InternetDialogDelegate]. The dialog will be animated from [view] if it is not
- * null.
+ * Creates a [InternetDialogDelegate]. The dialog will be animated from [expandable] if it is
+ * not null.
*/
fun create(
aboveStatusBar: Boolean,
canConfigMobileData: Boolean,
canConfigWifi: Boolean,
- view: View?
+ expandable: Expandable?
) {
if (dialog != null) {
if (DEBUG) {
@@ -67,20 +67,18 @@
dialogFactory
.create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope)
.createDialog()
- if (view != null) {
- dialogTransitionAnimator.showFromView(
- dialog!!,
- view,
- animateBackgroundBoundsChange = true,
- cuj =
- DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
- )
+ val controller =
+ expandable?.dialogTransitionController(
+ DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
)
- } else {
- dialog!!.show()
+ controller?.let {
+ dialogTransitionAnimator.show(
+ dialog!!,
+ controller,
+ animateBackgroundBoundsChange = true
+ )
}
+ ?: dialog?.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt
index 9e13a56..bf0f8f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt
@@ -45,7 +45,7 @@
}
AirplaneModeInteractor.SetResult.BLOCKED_BY_ECM -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS),
)
}
@@ -53,7 +53,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
index 0ad520b..14fc57c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
@@ -40,9 +40,12 @@
data.alarmClockInfo.showIntent != null
) {
val pendingIndent = data.alarmClockInfo.showIntent
- inputHandler.handle(action.view, pendingIndent, true)
+ inputHandler.handle(action.expandable, pendingIndent, true)
} else {
- inputHandler.handle(action.view, Intent(AlarmClock.ACTION_SHOW_ALARMS))
+ inputHandler.handle(
+ action.expandable,
+ Intent(AlarmClock.ACTION_SHOW_ALARMS)
+ )
}
}
is QSTileUserAction.LongClick -> {}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
index 1e4eb38..d4b4fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
@@ -39,12 +39,12 @@
when (action) {
is QSTileUserAction.Click -> {
if (!data.isPluggedIn) {
- batteryController.setPowerSaveMode(!data.isPowerSaving, action.view)
+ batteryController.setPowerSaveMode(!data.isPowerSaving, action.expandable)
}
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt
index d183802..534bd73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt
@@ -45,7 +45,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_COLOR_CORRECTION_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
index a16ac36..9bdf631 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
@@ -29,9 +29,9 @@
import android.provider.Settings
import android.service.quicksettings.TileService
import android.view.IWindowManager
-import android.view.View
import android.view.WindowManager
import androidx.annotation.GuardedBy
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
@@ -65,20 +65,21 @@
@GuardedBy("token") private var isTokenGranted: Boolean = false
@GuardedBy("token") private var isShowingDialog: Boolean = false
- private val lastClickedView: AtomicReference<View> = AtomicReference<View>()
+ private val lastClickedExpandable: AtomicReference<Expandable> = AtomicReference<Expandable>()
override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) =
with(input) {
when (action) {
- is QSTileUserAction.Click -> click(action.view, data.tile.activityLaunchForClick)
+ is QSTileUserAction.Click ->
+ click(action.expandable, data.tile.activityLaunchForClick)
is QSTileUserAction.LongClick ->
- longClick(user, action.view, data.componentName, data.tile.state)
+ longClick(user, action.expandable, data.componentName, data.tile.state)
}
qsTileLogger.logCustomTileUserActionDelivered(tileSpec)
}
private suspend fun click(
- view: View?,
+ expandable: Expandable?,
activityLaunchForClick: PendingIntent?,
) {
grantToken()
@@ -86,10 +87,10 @@
// Bind active tile to deliver user action
serviceInteractor.bindOnClick()
if (activityLaunchForClick == null) {
- lastClickedView.set(view)
+ lastClickedExpandable.set(expandable)
serviceInteractor.onClick(token)
} else {
- qsTileIntentUserInputHandler.handle(view, activityLaunchForClick)
+ qsTileIntentUserInputHandler.handle(expandable, activityLaunchForClick)
}
} catch (e: RemoteException) {
qsTileLogger.logError(tileSpec, "Failed to deliver click", e)
@@ -117,10 +118,10 @@
if (!isTokenGranted) {
return
}
- qsTileIntentUserInputHandler.handle(lastClickedView.getAndSet(null), pendingIntent)
+ qsTileIntentUserInputHandler.handle(lastClickedExpandable.getAndSet(null), pendingIntent)
}
- fun clearLastClickedView() = lastClickedView.set(null)
+ fun clearLastClickedView() = lastClickedExpandable.set(null)
private fun grantToken() {
synchronized(token) {
@@ -142,7 +143,7 @@
private suspend fun longClick(
user: UserHandle,
- view: View?,
+ expandable: Expandable?,
componentName: ComponentName,
state: Int
) {
@@ -159,14 +160,14 @@
}
if (resolvedIntent == null) {
qsTileIntentUserInputHandler.handle(
- view,
+ expandable,
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(
Uri.fromParts(IntentFilter.SCHEME_PACKAGE, componentName.packageName, null)
)
)
} else {
- qsTileIntentUserInputHandler.handle(view, resolvedIntent)
+ qsTileIntentUserInputHandler.handle(expandable, resolvedIntent)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
index db8b1a5..d308ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
@@ -52,21 +52,22 @@
with(input) {
when (action) {
is QSTileUserAction.Click -> {
- // We animate from the touched view only if we are not on the keyguard
- val animateFromView: Boolean =
- action.view != null && !keyguardStateController.isShowing
+ // We animate from the touched expandable only if we are not on the keyguard
+ val animateFromExpandable: Boolean =
+ action.expandable != null && !keyguardStateController.isShowing
val runnable = Runnable {
val dialog: SystemUIDialog =
fontScalingDialogDelegateProvider.get().createDialog()
- if (animateFromView) {
- dialogTransitionAnimator.showFromView(
- dialog,
- action.view!!,
- DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
+ if (animateFromExpandable) {
+ action.expandable
+ ?.dialogTransitionController(
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
)
- )
+ ?.let { dialogTransitionAnimator.show(dialog, it) }
+ ?: dialog.show()
} else {
dialog.show()
}
@@ -84,7 +85,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_TEXT_READING_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
index 2620cd5..c0b089d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -49,13 +49,13 @@
aboveStatusBar = true,
accessPointController.canConfigMobileData(),
accessPointController.canConfigWifi(),
- action.view,
+ action.expandable,
)
}
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_WIFI_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt
index 43b58c8..d643273 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt
@@ -45,7 +45,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_COLOR_INVERSION_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
index 66705ea..77404aa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
@@ -64,7 +64,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
index 762f863..14dbe0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -44,7 +44,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt
index 8530926..34385ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt
@@ -42,7 +42,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
index 861faf5..a5dc66c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
@@ -75,34 +75,32 @@
// must be created and shown on the main thread, so we post it to the UI
// handler
withContext(coroutineContext) {
- val dialogContext = action.view?.context ?: context
val dialogDelegate =
DataSaverDialogDelegate(
systemUIDialogFactory,
- dialogContext,
+ context,
backgroundContext,
dataSaverController,
sharedPreferences
)
- val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext)
+ val dialog = systemUIDialogFactory.create(dialogDelegate, context)
- if (action.view != null) {
- dialogTransitionAnimator.showFromView(
- dialog,
- action.view!!,
+ action.expandable
+ ?.dialogTransitionController(
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG
)
)
- } else {
- dialog.show()
- }
+ ?.let { controller ->
+ dialogTransitionAnimator.show(dialog, controller)
+ }
+ ?: dialog.show()
}
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_DATA_SAVER_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
index d2bd09f..79766d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
@@ -18,10 +18,10 @@
import android.content.Context
import android.util.Log
-import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -68,14 +68,16 @@
is ScreenRecordTileModel.Recording ->
withContext(backgroundContext) { recordingController.stopRecording() }
is ScreenRecordTileModel.DoingNothing ->
- withContext(mainContext) { showPrompt(action.view, user.identifier) }
+ withContext(mainContext) {
+ showPrompt(action.expandable, user.identifier)
+ }
}
}
is QSTileUserAction.LongClick -> {} // no-op
}
}
- private fun showPrompt(view: View?, userId: Int) {
+ private fun showPrompt(expandable: Expandable?, userId: Int) {
// Create the recording dialog that will collapse the shade only if we start the recording.
val onStartRecordingClicked = Runnable {
// We dismiss the shade. Since starting the recording will also dismiss the dialog, we
@@ -99,21 +101,29 @@
return
}
- // We animate from the touched view only if we are not on the keyguard, given that if we
+ // We animate from the touched expandable only if we are not on the keyguard, given that if
+ // we
// are we will dismiss it which will also collapse the shade.
- val shouldAnimateFromView = view != null && !keyguardInteractor.isKeyguardShowing()
+ val shouldAnimateFromExpandable =
+ expandable != null && !keyguardInteractor.isKeyguardShowing()
val dismissAction =
ActivityStarter.OnDismissAction {
- if (shouldAnimateFromView) {
- dialogTransitionAnimator.showFromView(
- dialog,
- view!!,
- DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
- ),
- animateBackgroundBoundsChange = true
- )
+ if (shouldAnimateFromExpandable) {
+ val controller =
+ expandable?.dialogTransitionController(
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ controller?.let {
+ dialogTransitionAnimator.show(
+ dialog,
+ controller,
+ animateBackgroundBoundsChange = true,
+ )
+ }
+ ?: dialog.show()
} else {
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt
index 9711cb8..f22a426 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt
@@ -80,7 +80,7 @@
}
)
}
- qsTileIntentUserActionHandler.handle(action.view, longClickIntent)
+ qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt
index 00d7a62..f8dd1730 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt
@@ -50,7 +50,7 @@
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_DARK_THEME_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt
index f765f8b..031e4d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt
@@ -44,7 +44,7 @@
is QSTileUserAction.LongClick -> {
if (data is WorkModeTileModel.HasActiveProfile) {
qsTileIntentUserActionHandler.handle(
- action.view,
+ action.expandable,
Intent(Settings.ACTION_MANAGED_PROFILE_SETTINGS)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
index a145042..acb2936 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
@@ -16,12 +16,12 @@
package com.android.systemui.qs.tiles.viewmodel
-import android.view.View
+import com.android.systemui.animation.Expandable
sealed interface QSTileUserAction {
- val view: View?
+ val expandable: Expandable?
- class Click(override val view: View?) : QSTileUserAction
- class LongClick(override val view: View?) : QSTileUserAction
+ class Click(override val expandable: Expandable?) : QSTileUserAction
+ class LongClick(override val expandable: Expandable?) : QSTileUserAction
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 5a389f3..b88c1e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,10 +19,10 @@
import android.content.Context
import android.os.UserHandle
import android.util.Log
-import android.view.View
import androidx.annotation.GuardedBy
import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.qs.QSTile
@@ -126,21 +126,21 @@
synchronized(callbacks) { callbacks.clear() }
}
- override fun click(view: View?) {
+ override fun click(expandable: Expandable?) {
if (isActionSupported(QSTileState.UserAction.CLICK)) {
- qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+ qsTileViewModel.onActionPerformed(QSTileUserAction.Click(expandable))
}
}
- override fun secondaryClick(view: View?) {
+ override fun secondaryClick(expandable: Expandable?) {
if (isActionSupported(QSTileState.UserAction.CLICK)) {
- qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+ qsTileViewModel.onActionPerformed(QSTileUserAction.Click(expandable))
}
}
- override fun longClick(view: View?) {
+ override fun longClick(expandable: Expandable?) {
if (isActionSupported(QSTileState.UserAction.LONG_CLICK)) {
- qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(view))
+ qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(expandable))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 3d86e3c..63acbb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -49,21 +49,44 @@
import kotlinx.coroutines.flow.Flow
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.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
// TODO(307945185) Split View concerns into a ViewBinder
/** Adapter to use between Scene system and [QSImpl] */
interface QSSceneAdapter {
- /** Whether [QSImpl] is currently customizing */
+
+ /**
+ * Whether we are currently customizing or entering the customizer.
+ *
+ * @see CustomizerState.isCustomizing
+ */
val isCustomizing: StateFlow<Boolean>
/**
+ * Whether the customizer is showing. This includes animating into and out of it.
+ *
+ * @see CustomizerState.isShowing
+ */
+ val isCustomizerShowing: StateFlow<Boolean>
+
+ /**
+ * The duration of the current animation in/out of customizer. If not in an animating state,
+ * this duration is 0 (to match show/hide immediately).
+ *
+ * @see CustomizerState.Animating.animationDuration
+ */
+ val customizerAnimationDuration: StateFlow<Int>
+
+ /**
* A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by
* the interactor.
*/
@@ -181,8 +204,35 @@
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
- private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isCustomizing = _isCustomizing.asStateFlow()
+ private val _customizingState: MutableStateFlow<CustomizerState> =
+ MutableStateFlow(CustomizerState.Hidden)
+ val customizerState = _customizingState.asStateFlow()
+
+ override val isCustomizing: StateFlow<Boolean> =
+ customizerState
+ .map { it.isCustomizing }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ customizerState.value.isCustomizing,
+ )
+ override val isCustomizerShowing: StateFlow<Boolean> =
+ customizerState
+ .map { it.isShowing }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ customizerState.value.isShowing
+ )
+ override val customizerAnimationDuration: StateFlow<Int> =
+ customizerState
+ .map { (it as? CustomizerState.Animating)?.animationDuration?.toInt() ?: 0 }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ (customizerState.value as? CustomizerState.Animating)?.animationDuration?.toInt()
+ ?: 0,
+ )
private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null)
val qsImpl = _qsImpl.asStateFlow()
@@ -209,9 +259,9 @@
dumpManager.registerDumpable(this)
applicationScope.launch {
launch {
- state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
+ state.sample(_customizingState, ::Pair).collect { (state, customizing) ->
qsImpl.value?.apply {
- if (state != QSSceneAdapter.State.QS && customizing) {
+ if (state != QSSceneAdapter.State.QS && customizing.isShowing) {
this@apply.closeCustomizerImmediately()
}
applyState(state)
@@ -243,14 +293,38 @@
}
}
- override fun setCustomizerAnimating(animating: Boolean) {}
+ override fun setCustomizerAnimating(animating: Boolean) {
+ if (_customizingState.value is CustomizerState.Animating && !animating) {
+ _customizingState.update {
+ if (it is CustomizerState.AnimatingIntoCustomizer) {
+ CustomizerState.Showing
+ } else {
+ CustomizerState.Hidden
+ }
+ }
+ }
+ }
override fun setCustomizerShowing(showing: Boolean) {
- _isCustomizing.value = showing
+ setCustomizerShowing(showing, 0L)
}
override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) {
- setCustomizerShowing(showing)
+ _customizingState.update { _ ->
+ if (showing) {
+ if (animationDuration > 0) {
+ CustomizerState.AnimatingIntoCustomizer(animationDuration)
+ } else {
+ CustomizerState.Showing
+ }
+ } else {
+ if (animationDuration > 0) {
+ CustomizerState.AnimatingOutOfCustomizer(animationDuration)
+ } else {
+ CustomizerState.Hidden
+ }
+ }
+ }
}
override fun setDetailShowing(showing: Boolean) {}
@@ -302,9 +376,50 @@
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
println("Last state: ${state.value}")
- println("Customizing: ${isCustomizing.value}")
+ println("CustomizerState: ${_customizingState.value}")
println("QQS height: $qqsHeight")
println("QS height: $qsHeight")
}
}
}
+
+/** Current state of the customizer */
+sealed interface CustomizerState {
+
+ /**
+ * This indicates that some part of the customizer is showing. It could be animating in or out.
+ */
+ val isShowing: Boolean
+ get() = true
+
+ /**
+ * This indicates that we are currently customizing or animating into it. In particular, when
+ * animating out, this is false.
+ *
+ * @see QSCustomizer.isCustomizing
+ */
+ val isCustomizing: Boolean
+ get() = false
+
+ sealed interface Animating : CustomizerState {
+ val animationDuration: Long
+ }
+
+ /** Customizer is completely hidden, and not animating */
+ data object Hidden : CustomizerState {
+ override val isShowing = false
+ }
+
+ /** Customizer is completely showing, and not animating */
+ data object Showing : CustomizerState {
+ override val isCustomizing = true
+ }
+
+ /** Animating from [Hidden] into [Showing]. */
+ data class AnimatingIntoCustomizer(override val animationDuration: Long) : Animating {
+ override val isCustomizing = true
+ }
+
+ /** Animating from [Showing] into [Hidden]. */
+ data class AnimatingOutOfCustomizer(override val animationDuration: Long) : Animating
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 257c4d5..17698f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -79,13 +79,13 @@
combine(
deviceEntryInteractor.isUnlocked,
deviceEntryInteractor.canSwipeToEnter,
- qsSceneAdapter.isCustomizing,
+ qsSceneAdapter.isCustomizerShowing,
backScene,
- ) { isUnlocked, canSwipeToDismiss, isCustomizing, backScene ->
+ ) { isUnlocked, canSwipeToDismiss, isCustomizerShowing, backScene ->
destinationScenes(
isUnlocked,
canSwipeToDismiss,
- isCustomizing,
+ isCustomizerShowing,
backScene,
)
}
@@ -96,7 +96,7 @@
destinationScenes(
isUnlocked = deviceEntryInteractor.isUnlocked.value,
canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
- isCustomizing = qsSceneAdapter.isCustomizing.value,
+ isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
backScene = backScene.value,
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index b92e8eb..0673dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -74,6 +74,7 @@
import androidx.annotation.NonNull;
+import com.android.compose.animation.scene.SceneKey;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
@@ -103,6 +104,8 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.shared.model.ShadeMode;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.QuickStepContract;
@@ -155,6 +158,7 @@
private final ScreenPinningRequest mScreenPinningRequest;
private final NotificationShadeWindowController mStatusBarWinController;
private final Provider<SceneInteractor> mSceneInteractor;
+ private final Provider<ShadeInteractor> mShadeInteractor;
private final Runnable mConnectionRunnable = () ->
internalConnectToCurrentUser("runnable: startConnectionToCurrentUser");
@@ -243,7 +247,9 @@
// Gesture was too short to be picked up by scene container touch
// handling; programmatically start the transition to shade scene.
mSceneInteractor.get().changeScene(
- Scenes.Shade, "short launcher swipe");
+ getShadeSceneKey(),
+ "short launcher swipe"
+ );
}
}
event.recycle();
@@ -261,7 +267,9 @@
"trackpad swipe");
} else if (action == ACTION_UP) {
mSceneInteractor.get().changeScene(
- Scenes.Shade, "short trackpad swipe");
+ getShadeSceneKey(),
+ "short trackpad swipe"
+ );
}
mStatusBarWinController.getWindowRootView().dispatchTouchEvent(event);
} else {
@@ -618,6 +626,7 @@
NotificationShadeWindowController statusBarWinController,
SysUiState sysUiState,
Provider<SceneInteractor> sceneInteractor,
+ Provider<ShadeInteractor> shadeInteractor,
UserTracker userTracker,
WakefulnessLifecycle wakefulnessLifecycle,
UiEventLogger uiEventLogger,
@@ -644,6 +653,7 @@
mScreenPinningRequest = screenPinningRequest;
mStatusBarWinController = statusBarWinController;
mSceneInteractor = sceneInteractor;
+ mShadeInteractor = shadeInteractor;
mUserTracker = userTracker;
mConnectionBackoffAttempts = 0;
mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
@@ -909,6 +919,12 @@
}
}
+ private SceneKey getShadeSceneKey() {
+ return mShadeInteractor.get().getShadeMode().getValue() == ShadeMode.dual()
+ ? Scenes.NotificationsShade
+ : Scenes.Shade;
+ }
+
private void notifyHomeRotationEnabled(boolean enabled) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onHomeRotationEnabled(enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 063a52c..aa8ecfc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.flag.DualShade
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -33,6 +34,7 @@
[
EmptySceneModule::class,
GoneSceneModule::class,
+ NotificationsShadeSceneModule::class,
QuickSettingsSceneModule::class,
ShadeSceneModule::class,
],
@@ -59,18 +61,22 @@
// Note that this list is in z-order. The first one is the bottom-most and the
// last one is top-most.
sceneKeys =
- listOf(
+ listOfNotNull(
Scenes.Gone,
Scenes.QuickSettings,
- Scenes.Shade,
+ Scenes.NotificationsShade.takeIf { DualShade.isEnabled },
+ Scenes.Shade.takeUnless { DualShade.isEnabled },
),
initialSceneKey = Scenes.Gone,
navigationDistances =
mapOf(
- Scenes.Gone to 0,
- Scenes.Shade to 1,
- Scenes.QuickSettings to 2,
- ),
+ Scenes.Gone to 0,
+ Scenes.NotificationsShade to 1.takeIf { DualShade.isEnabled },
+ Scenes.Shade to 1.takeUnless { DualShade.isEnabled },
+ Scenes.QuickSettings to 2,
+ )
+ .filterValues { it != null }
+ .mapValues { checkNotNull(it.value) }
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index cd1b965..551aa12 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -22,6 +22,7 @@
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.flag.DualShade
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -40,6 +41,7 @@
LockscreenSceneModule::class,
QuickSettingsSceneModule::class,
ShadeSceneModule::class,
+ NotificationsShadeSceneModule::class,
],
)
interface SceneContainerFrameworkModule {
@@ -61,27 +63,31 @@
@Provides
fun containerConfig(): SceneContainerConfig {
return SceneContainerConfig(
- // Note that this list is in z-order. The first one is the bottom-most and the
- // last one is top-most.
+ // Note that this list is in z-order. The first one is the bottom-most and the last
+ // one is top-most.
sceneKeys =
- listOf(
+ listOfNotNull(
Scenes.Gone,
Scenes.Communal,
Scenes.Lockscreen,
Scenes.Bouncer,
Scenes.QuickSettings,
- Scenes.Shade,
+ Scenes.NotificationsShade.takeIf { DualShade.isEnabled },
+ Scenes.Shade.takeUnless { DualShade.isEnabled },
),
initialSceneKey = Scenes.Lockscreen,
navigationDistances =
mapOf(
- Scenes.Gone to 0,
- Scenes.Lockscreen to 0,
- Scenes.Communal to 1,
- Scenes.Shade to 2,
- Scenes.QuickSettings to 3,
- Scenes.Bouncer to 4,
- ),
+ Scenes.Gone to 0,
+ Scenes.Lockscreen to 0,
+ Scenes.Communal to 1,
+ Scenes.NotificationsShade to 2.takeIf { DualShade.isEnabled },
+ Scenes.Shade to 2.takeUnless { DualShade.isEnabled },
+ Scenes.QuickSettings to 3,
+ Scenes.Bouncer to 4,
+ )
+ .filterValues { it != null }
+ .mapValues { checkNotNull(it.value) }
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index c59018b..ace4491 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -125,6 +125,7 @@
Scenes.Communal -> true
Scenes.Gone -> true
Scenes.Lockscreen -> true
+ Scenes.NotificationsShade -> false
Scenes.QuickSettings -> false
Scenes.Shade -> false
else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!")
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index e0b8b85..de3b87a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -51,7 +51,7 @@
private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository,
private val keyguardRepository: KeyguardRepository,
private val headsUpManager: HeadsUpManager,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
sceneInteractorProvider: Provider<SceneInteractor>,
) : CoreStartable {
@@ -77,11 +77,14 @@
when (state) {
is ObservableTransitionState.Idle ->
state.currentScene == Scenes.Shade ||
+ state.currentScene == Scenes.NotificationsShade ||
state.currentScene == Scenes.Lockscreen
is ObservableTransitionState.Transition ->
state.toScene == Scenes.Shade ||
+ state.toScene == Scenes.NotificationsShade ||
state.toScene == Scenes.Lockscreen ||
state.fromScene == Scenes.Shade ||
+ state.fromScene == Scenes.NotificationsShade ||
state.fromScene == Scenes.Lockscreen
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index f64e0af..9e57964 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -537,6 +537,7 @@
Scenes.Lockscreen -> true
Scenes.Bouncer -> false
Scenes.Shade -> false
+ Scenes.NotificationsShade -> false
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
index 73fcca8..08f1be9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
@@ -42,11 +42,32 @@
/** The lockscreen is the scene that shows when the device is locked. */
@JvmField val Lockscreen = SceneKey("lockscreen")
+ /**
+ * The notifications shade scene primarily shows a scrollable list of notifications as an
+ * overlay UI.
+ *
+ * It's used only in the dual shade configuration, where there are two separate shades: one for
+ * notifications (this scene) and another for quick settings (where a separate scene is used).
+ *
+ * It's not used in the single/accordion configuration (swipe down once to reveal the shade,
+ * swipe down again the to expand quick settings) and for the "split" shade configuration (on
+ * large screens or unfolded foldables, where notifications and quick settings are shown
+ * side-by-side in their own columns).
+ */
+ @JvmField val NotificationsShade = SceneKey("notifications_shade")
+
/** The quick settings scene shows the quick setting tiles. */
@JvmField val QuickSettings = SceneKey("quick_settings")
/**
- * The shade is the scene whose primary purpose is to show a scrollable list of notifications.
+ * The shade is the scene that shows a scrollable list of notifications and the minimized
+ * version of quick settings (AKA "quick quick settings" or "QQS").
+ *
+ * This scene is used for single/accordion configuration (swipe down once to reveal the shade,
+ * swipe down again the to expand quick settings) and for the "split" shade configuration (on
+ * large screens or unfolded foldables, where notifications and quick settings are shown
+ * side-by-side in their own columns). For the dual shade configuration, where there are two
+ * separate shades: one for notifications and one for quick settings, other scenes are used.
*/
@JvmField val Shade = SceneKey("shade")
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index 451fd67..e4435cc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -51,7 +51,7 @@
private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> {
return buildMap {
- if (shadeMode == ShadeMode.Single) {
+ if (shadeMode is ShadeMode.Single) {
this[
Swipe(
pointerCount = 2,
@@ -60,7 +60,10 @@
)] = UserActionResult(Scenes.QuickSettings)
}
- this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
+ this[Swipe(direction = SwipeDirection.Down)] =
+ UserActionResult(
+ if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index ef7829f..7fbede47 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -124,6 +124,7 @@
when (toScene) {
Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK
Scenes.Gone -> Classifier.UNLOCK
+ Scenes.NotificationsShade -> Classifier.NOTIFICATION_DRAG_DOWN
Scenes.Shade -> Classifier.NOTIFICATION_DRAG_DOWN
Scenes.QuickSettings -> Classifier.QUICK_SETTINGS
else -> null
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
index 3462993..864e39a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -33,7 +33,7 @@
get() = shadeInteractor.isQsExpanded.value
override val isCustomizing: Boolean
- get() = qsSceneAdapter.isCustomizing.value
+ get() = qsSceneAdapter.isCustomizerShowing.value
@Deprecated("specific to legacy touch handling")
override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 8c15817..5cc30bd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -32,6 +32,7 @@
import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.VibratorHelper
@@ -194,7 +195,11 @@
}
override fun expandToNotifications() {
- sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade")
+ val shadeMode = shadeInteractor.shadeMode.value
+ sceneInteractor.changeScene(
+ if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
+ "ShadeController.animateExpandShade"
+ )
}
override fun expandToQs() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index b934d63..7e1a310 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -219,7 +220,7 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
- val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+ val _shadeMode = MutableStateFlow(if (DualShade.isEnabled) ShadeMode.Dual else ShadeMode.Single)
override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
override fun setShadeMode(shadeMode: ShadeMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
index 3a8ba7a..c9949cd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -19,6 +19,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,7 +35,7 @@
override fun animateCollapseQs(fullyCollapse: Boolean) {
if (shadeInteractor.isQsExpanded.value) {
val key =
- if (fullyCollapse) {
+ if (fullyCollapse || shadeInteractor.shadeMode.value is ShadeMode.Dual) {
if (deviceEntryInteractor.isDeviceEntered.value) {
Scenes.Gone
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 6a8b9ee..9885fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -21,6 +21,7 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -95,8 +96,9 @@
}
private fun changeToShadeScene() {
+ val shadeMode = shadeInteractor.shadeMode.value
sceneInteractor.changeScene(
- Scenes.Shade,
+ if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
"ShadeLockscreenInteractorImpl.expandToNotifications",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index f3802da..3f4bcba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -29,6 +29,7 @@
import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.transition.ScrimShadeTransitionController
import com.android.systemui.statusbar.policy.SplitShadeStateController
@@ -67,19 +68,24 @@
private fun hydrateShadeExpansionStateManager() {
if (SceneContainerFlag.isEnabled) {
combine(
- panelExpansionInteractorProvider.get().legacyPanelExpansion,
- sceneInteractorProvider.get().isTransitionUserInputOngoing,
- ) { panelExpansion, tracking ->
- shadeExpansionStateManager.onPanelExpansionChanged(
- fraction = panelExpansion,
- expanded = panelExpansion > 0f,
- tracking = tracking,
- )
- }.launchIn(applicationScope)
+ panelExpansionInteractorProvider.get().legacyPanelExpansion,
+ sceneInteractorProvider.get().isTransitionUserInputOngoing,
+ ) { panelExpansion, tracking ->
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = panelExpansion,
+ expanded = panelExpansion > 0f,
+ tracking = tracking,
+ )
+ }
+ .launchIn(applicationScope)
}
}
private fun hydrateShadeMode() {
+ if (DualShade.isEnabled) {
+ shadeRepository.setShadeMode(ShadeMode.Dual)
+ return
+ }
applicationScope.launch {
configurationRepository.onAnyConfigurationChange
// Force initial collection.
@@ -90,11 +96,7 @@
}
.collect { isSplitShade ->
shadeRepository.setShadeMode(
- if (isSplitShade) {
- ShadeMode.Split
- } else {
- ShadeMode.Single
- }
+ if (isSplitShade) ShadeMode.Split else ShadeMode.Single
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
index 3451eaf..8214a24 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -38,4 +38,8 @@
* a space on a small screen or folded device.
*/
data object Dual : ShadeMode
+
+ companion object {
+ @JvmStatic fun dual(): Dual = Dual
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModel.kt
new file mode 100644
index 0000000..ba01776
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state and handles user input for the Notifications Shade scene. */
+@SysUISingleton
+class NotificationsShadeSceneViewModel
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ overlayShadeViewModel: OverlayShadeViewModel,
+) {
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ overlayShadeViewModel.backgroundScene
+ .map(::destinationScenes)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = destinationScenes(overlayShadeViewModel.backgroundScene.value),
+ )
+
+ private fun destinationScenes(backgroundScene: SceneKey): Map<UserAction, UserActionResult> {
+ return mapOf(
+ Swipe.Up to backgroundScene,
+ Back to backgroundScene,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 5b76acb..ac76bec 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -72,13 +72,13 @@
deviceEntryInteractor.isUnlocked,
deviceEntryInteractor.canSwipeToEnter,
shadeInteractor.shadeMode,
- qsSceneAdapter.isCustomizing
- ) { isUnlocked, canSwipeToDismiss, shadeMode, isCustomizing ->
+ qsSceneAdapter.isCustomizerShowing
+ ) { isUnlocked, canSwipeToDismiss, shadeMode, isCustomizerShowing ->
destinationScenes(
isUnlocked = isUnlocked,
canSwipeToDismiss = canSwipeToDismiss,
shadeMode = shadeMode,
- isCustomizing = isCustomizing
+ isCustomizing = isCustomizerShowing
)
}
.stateIn(
@@ -89,7 +89,7 @@
isUnlocked = deviceEntryInteractor.isUnlocked.value,
canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
shadeMode = shadeInteractor.shadeMode.value,
- isCustomizing = qsSceneAdapter.isCustomizing.value,
+ isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 78e108d..0d8030f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -97,7 +97,7 @@
public final class KeyboardShortcutListSearch {
private static final String TAG = KeyboardShortcutListSearch.class.getSimpleName();
private static final Object sLock = new Object();
- @VisibleForTesting static KeyboardShortcutListSearch sInstance;
+ @VisibleForTesting public static KeyboardShortcutListSearch sInstance;
private static int SHORTCUT_SYSTEM_INDEX = 0;
private static int SHORTCUT_INPUT_INDEX = 1;
@@ -136,7 +136,7 @@
};
private final Handler mHandler = new Handler(Looper.getMainLooper());
- @VisibleForTesting Context mContext;
+ @VisibleForTesting public Context mContext;
private final IPackageManager mPackageManager;
@VisibleForTesting BottomSheetDialog mKeyboardShortcutsBottomSheetDialog;
@@ -414,7 +414,7 @@
private boolean mImeShortcutsReceived;
@VisibleForTesting
- void showKeyboardShortcuts(int deviceId) {
+ public void showKeyboardShortcuts(int deviceId) {
retrieveKeyCharacterMap(deviceId);
mAppShortcutsReceived = false;
mImeShortcutsReceived = false;
@@ -502,7 +502,8 @@
return keyboardShortcutMultiMappingGroups;
}
- private void dismissKeyboardShortcuts() {
+ @VisibleForTesting
+ public void dismissKeyboardShortcuts() {
if (mKeyboardShortcutsBottomSheetDialog != null) {
mKeyboardShortcutsBottomSheetDialog.dismiss();
mKeyboardShortcutsBottomSheetDialog = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 90567d8..21f608e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -79,7 +79,7 @@
public final class KeyboardShortcuts {
private static final String TAG = KeyboardShortcuts.class.getSimpleName();
private static final Object sLock = new Object();
- @VisibleForTesting static KeyboardShortcuts sInstance;
+ @VisibleForTesting public static KeyboardShortcuts sInstance;
private WindowManager mWindowManager;
private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
@@ -93,7 +93,7 @@
};
private final Handler mHandler = new Handler(Looper.getMainLooper());
- @VisibleForTesting Context mContext;
+ @VisibleForTesting public Context mContext;
private final IPackageManager mPackageManager;
private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
@@ -373,7 +373,7 @@
}
@VisibleForTesting
- void showKeyboardShortcuts(int deviceId) {
+ public void showKeyboardShortcuts(int deviceId) {
retrieveKeyCharacterMap(deviceId);
mReceivedAppShortcutGroups = null;
mReceivedImeShortcutGroups = null;
@@ -407,7 +407,8 @@
showKeyboardShortcutsDialog(shortcutGroups);
}
- private void dismissKeyboardShortcuts() {
+ @VisibleForTesting
+ public void dismissKeyboardShortcuts() {
if (mKeyboardShortcutsDialog != null) {
mKeyboardShortcutsDialog.dismiss();
mKeyboardShortcutsDialog = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
index 1cfb400..815f1fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar;
+import static com.android.systemui.Flags.keyboardShortcutHelperRewrite;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -25,21 +27,22 @@
import javax.inject.Inject;
-/**
- * Receiver for the Keyboard Shortcuts Helper.
- */
+/** Receiver for the Keyboard Shortcuts Helper. */
public class KeyboardShortcutsReceiver extends BroadcastReceiver {
- private boolean mIsShortcutListSearchEnabled;
+ private final FeatureFlags mFeatureFlags;
@Inject
public KeyboardShortcutsReceiver(FeatureFlags featureFlags) {
- mIsShortcutListSearchEnabled = featureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT);
+ mFeatureFlags = featureFlags;
}
@Override
public void onReceive(Context context, Intent intent) {
- if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(context)) {
+ if (keyboardShortcutHelperRewrite()) {
+ return;
+ }
+ if (isTabletLayoutFlagEnabled() && Utilities.isLargeScreen(context)) {
if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
KeyboardShortcutListSearch.show(context, -1 /* deviceId unknown */);
} else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
@@ -53,4 +56,8 @@
}
}
}
+
+ private boolean isTabletLayoutFlagEnabled() {
+ return mFeatureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 7c1101b..d7d3732 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -382,16 +382,15 @@
private void clearCurrentMediaNotificationSession() {
mMediaMetadata = null;
- mBackgroundExecutor.execute(() -> {
- if (mMediaController != null) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
- + mMediaController.getPackageName());
- }
- mMediaController.unregisterCallback(mMediaListener);
- mMediaController = null;
+ if (mMediaController != null) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ + mMediaController.getPackageName());
}
- });
+ // TODO(b/336612071): move to background thread
+ mMediaController.unregisterCallback(mMediaListener);
+ }
+ mMediaController = null;
}
public interface MediaListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 4f8c3caa..96a50f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -677,6 +677,7 @@
Scenes.Bouncer, StatusBarState.KEYGUARD,
Scenes.Communal, StatusBarState.KEYGUARD,
Scenes.Shade, StatusBarState.SHADE_LOCKED,
+ Scenes.NotificationsShade, StatusBarState.SHADE_LOCKED,
Scenes.QuickSettings, StatusBarState.SHADE_LOCKED,
Scenes.Gone, StatusBarState.SHADE
);
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 7630d43..be6bef7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -26,10 +26,12 @@
import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
+import static com.android.systemui.Flags.keyboardShortcutHelperRewrite;
import static com.android.systemui.Flags.lightRevealMigration;
import static com.android.systemui.Flags.newAodTransition;
import static com.android.systemui.Flags.truncatedStatusBarIconsFix;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
+import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -289,7 +291,6 @@
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
private final NotificationListContainer mNotifListContainer;
- private final boolean mIsShortcutListSearchEnabled;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
new KeyguardStateController.Callback() {
@@ -789,7 +790,6 @@
mStatusBarSignalPolicy = statusBarSignalPolicy;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mFeatureFlags = featureFlags;
- mIsShortcutListSearchEnabled = featureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT);
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mMainExecutor = delayableExecutor;
mMessageRouter = messageRouter;
@@ -820,10 +820,13 @@
// TODO(b/190746471): Find a better home for this.
DateTimeView.setReceiverHandler(timeTickHandler);
- mMessageRouter.subscribeTo(KeyboardShortcutsMessage.class,
- data -> toggleKeyboardShortcuts(data.mDeviceId));
- mMessageRouter.subscribeTo(MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU,
- id -> dismissKeyboardShortcuts());
+ if (!keyboardShortcutHelperRewrite()) {
+ mMessageRouter.subscribeTo(
+ KeyboardShortcutsMessage.class,
+ data -> toggleKeyboardShortcuts(data.mDeviceId));
+ mMessageRouter.subscribeTo(
+ MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU, id -> dismissKeyboardShortcuts());
+ }
mMessageRouter.subscribeTo(AnimateExpandSettingsPanelMessage.class,
data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel));
mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT,
@@ -1872,10 +1875,12 @@
String action = intent.getAction();
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
- if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) {
- KeyboardShortcutListSearch.dismiss();
- } else {
- KeyboardShortcuts.dismiss();
+ if (!keyboardShortcutHelperRewrite()) {
+ if (shouldUseTabletKeyboardShortcuts()) {
+ KeyboardShortcutListSearch.dismiss();
+ } else {
+ KeyboardShortcuts.dismiss();
+ }
}
mRemoteInputManager.closeRemoteInputs();
if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
@@ -2345,6 +2350,7 @@
} else if (mState == StatusBarState.KEYGUARD
&& !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
&& mStatusBarKeyguardViewManager.isSecure()) {
+ Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
}
}
@@ -2945,7 +2951,7 @@
}
protected void toggleKeyboardShortcuts(int deviceId) {
- if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) {
+ if (shouldUseTabletKeyboardShortcuts()) {
KeyboardShortcutListSearch.toggle(mContext, deviceId);
} else {
KeyboardShortcuts.toggle(mContext, deviceId);
@@ -2953,13 +2959,18 @@
}
protected void dismissKeyboardShortcuts() {
- if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) {
+ if (shouldUseTabletKeyboardShortcuts()) {
KeyboardShortcutListSearch.dismiss();
} else {
KeyboardShortcuts.dismiss();
}
}
+ private boolean shouldUseTabletKeyboardShortcuts() {
+ return mFeatureFlags.isEnabled(SHORTCUT_LIST_SEARCH_LAYOUT)
+ && Utilities.isLargeScreen(mContext);
+ }
+
private void clearNotificationEffects() {
try {
mBarService.clearNotificationEffects();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 5deb08a7..cff46ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
@@ -277,6 +278,15 @@
addView(view, viewIndex, createLayoutParams());
}
+ /** Adds a bindable icon to the demo mode view. */
+ public void addBindableIcon(StatusBarIconHolder.BindableIconHolder holder) {
+ // This doesn't do any correct ordering, and also doesn't check if we already have an
+ // existing icon for the slot. But since we hope to remove this class soon, we won't spend
+ // the time adding that logic.
+ ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
+ addView(view, createLayoutParams());
+ }
+
public void onRemoveIcon(StatusIconDisplayable view) {
if (view.getSlot().equals("wifi")) {
if (view instanceof ModernStatusBarWifiView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
index bef0b28..08a890d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -169,16 +169,19 @@
* StatusBarIconController will register all available bindable icons on init (see
* [BindableIconsRepository]), and will ignore any call to setIcon for these.
*
- * [initializer] a view creator that can bind the relevant view models to the created view.
+ * @property initializer a view creator that can bind the relevant view models to the created
+ * view.
+ * @property slot the name of the slot that this holder is used for.
*/
- class BindableIconHolder(val initializer: ModernStatusBarViewCreator) : StatusBarIconHolder() {
+ class BindableIconHolder(val initializer: ModernStatusBarViewCreator, val slot: String) :
+ StatusBarIconHolder() {
override var type: Int = TYPE_BINDABLE
/** This is unused, as bindable icons use their own view binders to control visibility */
override var isVisible: Boolean = true
override fun toString(): String {
- return ("StatusBarIconHolder(type=BINDABLE)")
+ return ("StatusBarIconHolder(type=BINDABLE, slot=$slot)")
}
}
}
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 7301b87..f0dab3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -744,6 +744,7 @@
public void showBouncer(boolean scrimmed) {
if (DeviceEntryUdfpsRefactor.isEnabled()) {
if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) {
+ Log.d(TAG, "showBouncer:alternateBouncer.forceShow()");
mAlternateBouncerInteractor.forceShow();
updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
} else {
@@ -869,6 +870,7 @@
}
if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ Log.d(TAG, "dismissWithAction:alternateBouncer.forceShow()");
mAlternateBouncerInteractor.forceShow();
updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
index 0ed9420..5ad7376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.DemoStatusIcons;
import com.android.systemui.statusbar.phone.StatusBarIconHolder;
+import com.android.systemui.statusbar.phone.StatusBarIconHolder.BindableIconHolder;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
@@ -49,7 +50,9 @@
import com.android.systemui.util.Assert;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Turns info from StatusBarIconController into ImageViews in a ViewGroup.
@@ -60,6 +63,11 @@
private final LocationBasedWifiViewModel mWifiViewModel;
private final MobileIconsViewModel mMobileIconsViewModel;
+ /**
+ * Stores the list of bindable icons that have been added, keyed on slot name. This ensures
+ * we don't accidentally add the same bindable icon twice.
+ */
+ private final Map<String, BindableIconHolder> mBindableIcons = new HashMap<>();
protected final Context mContext;
protected int mIconSize;
// Whether or not these icons show up in dumpsys
@@ -142,7 +150,7 @@
case TYPE_MOBILE_NEW -> addNewMobileIcon(index, slot, holder.getTag());
case TYPE_BINDABLE ->
// Safe cast, since only BindableIconHolders can set this tag on themselves
- addBindableIcon((StatusBarIconHolder.BindableIconHolder) holder, index);
+ addBindableIcon((BindableIconHolder) holder, index);
default -> null;
};
}
@@ -162,10 +170,14 @@
* icon view, we can simply create the icon when requested and allow the
* ViewBinder to control its visual state.
*/
- protected StatusIconDisplayable addBindableIcon(StatusBarIconHolder.BindableIconHolder holder,
+ protected StatusIconDisplayable addBindableIcon(BindableIconHolder holder,
int index) {
+ mBindableIcons.put(holder.getSlot(), holder);
ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
mGroup.addView(view, index, onCreateLayoutParams());
+ if (mIsInDemoMode) {
+ mDemoStatusIcons.addBindableIcon(holder);
+ }
return view;
}
@@ -278,6 +290,9 @@
if (mDemoStatusIcons == null) {
mDemoStatusIcons = createDemoStatusIcons();
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+ for (BindableIconHolder holder : mBindableIcons.values()) {
+ mDemoStatusIcons.addBindableIcon(holder);
+ }
}
mDemoStatusIcons.onDemoModeStarted();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
index 92d90af..fabf858d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
@@ -213,7 +213,8 @@
StatusBarIconHolder existingHolder = mStatusBarIconList.getIconHolder(icon.getSlot(), 0);
// Expected to be null
if (existingHolder == null) {
- BindableIconHolder bindableIcon = new BindableIconHolder(icon.getInitializer());
+ BindableIconHolder bindableIcon =
+ new BindableIconHolder(icon.getInitializer(), icon.getSlot());
setIcon(icon.getSlot(), bindableIcon);
} else {
Log.e(TAG, "addBindableIcon called, but icon has already been added. Ignoring");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index b80ff38..226a84a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -41,6 +41,8 @@
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl
import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModelImpl
@@ -83,8 +85,13 @@
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
- abstract fun deviceBasedSatelliteRepository(
+ abstract fun realDeviceBasedSatelliteRepository(
impl: DeviceBasedSatelliteRepositoryImpl
+ ): RealDeviceBasedSatelliteRepository
+
+ @Binds
+ abstract fun deviceBasedSatelliteRepository(
+ impl: DeviceBasedSatelliteRepositorySwitcher
): DeviceBasedSatelliteRepository
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index ad8b810..d38e834 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.satellite.data
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* Device-based satellite refers to the capability of a device to connect directly to a satellite
@@ -26,12 +26,22 @@
*/
interface DeviceBasedSatelliteRepository {
/** See [SatelliteConnectionState] for available states */
- val connectionState: Flow<SatelliteConnectionState>
+ val connectionState: StateFlow<SatelliteConnectionState>
/** 0-4 level (similar to wifi and mobile) */
// @IntRange(from = 0, to = 4)
- val signalStrength: Flow<Int>
+ val signalStrength: StateFlow<Int>
/** Clients must observe this property, as device-based satellite is location-dependent */
- val isSatelliteAllowedForCurrentLocation: Flow<Boolean>
+ val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
}
+
+/**
+ * A no-op interface used for Dagger bindings.
+ *
+ * [DeviceBasedSatelliteRepositorySwitcher] needs to inject both the real repository and the demo
+ * mode repository, both of which implement the [DeviceBasedSatelliteRepository] interface. To help
+ * distinguish the two for the switcher, [DeviceBasedSatelliteRepositoryImpl] will implement this
+ * [RealDeviceBasedSatelliteRepository] interface.
+ */
+interface RealDeviceBasedSatelliteRepository : DeviceBasedSatelliteRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
new file mode 100644
index 0000000..6b1bc65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.data
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A provider for the [DeviceBasedSatelliteRepository] interface that can choose between the Demo
+ * and Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo],
+ * which switches based on the latest information from [DemoModeController], and switches every flow
+ * in the interface to point to the currently-active provider. This allows us to put the demo mode
+ * interface in its own repository, completely separate from the real version, while still using all
+ * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
+ * something like this:
+ * ```
+ * RealRepository
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
+ * DemoRepository
+ * ```
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DeviceBasedSatelliteRepositorySwitcher
+@Inject
+constructor(
+ private val realImpl: RealDeviceBasedSatelliteRepository,
+ private val demoImpl: DemoDeviceBasedSatelliteRepository,
+ private val demoModeController: DemoModeController,
+ @Application scope: CoroutineScope,
+) : DeviceBasedSatelliteRepository {
+ private val isDemoMode =
+ conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+ // Don't care
+ }
+
+ override fun onDemoModeStarted() {
+ demoImpl.startProcessingCommands()
+ trySend(true)
+ }
+
+ override fun onDemoModeFinished() {
+ demoImpl.stopProcessingCommands()
+ trySend(false)
+ }
+ }
+
+ demoModeController.addCallback(callback)
+ awaitClose { demoModeController.removeCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+ @VisibleForTesting
+ val activeRepo: StateFlow<DeviceBasedSatelliteRepository> =
+ isDemoMode
+ .mapLatest { isDemoMode ->
+ if (isDemoMode) {
+ demoImpl
+ } else {
+ realImpl
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+
+ override val connectionState: StateFlow<SatelliteConnectionState> =
+ activeRepo
+ .flatMapLatest { it.connectionState }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.connectionState.value)
+
+ override val signalStrength: StateFlow<Int> =
+ activeRepo
+ .flatMapLatest { it.signalStrength }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.signalStrength.value)
+
+ override val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean> =
+ activeRepo
+ .flatMapLatest { it.isSatelliteAllowedForCurrentLocation }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realImpl.isSatelliteAllowedForCurrentLocation.value
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt
new file mode 100644
index 0000000..7ecc29b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.data.demo
+
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Reads the incoming demo commands and emits the satellite-related commands to [satelliteEvents]
+ * for the demo repository to consume.
+ */
+@SysUISingleton
+class DemoDeviceBasedSatelliteDataSource
+@Inject
+constructor(
+ demoModeController: DemoModeController,
+ @Application scope: CoroutineScope,
+) {
+ private val demoCommandStream = demoModeController.demoFlowForCommand(DemoMode.COMMAND_NETWORK)
+ private val _satelliteCommands =
+ demoCommandStream.map { args -> args.toSatelliteEvent() }.filterNotNull()
+
+ /** A flow that emits the demo commands that are satellite-related. */
+ val satelliteEvents =
+ _satelliteCommands.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_VALUE)
+
+ private fun Bundle.toSatelliteEvent(): DemoSatelliteEvent? {
+ val satellite = getString("satellite") ?: return null
+ if (satellite != "show") {
+ return null
+ }
+
+ return DemoSatelliteEvent(
+ connectionState = getString("connection").toConnectionState(),
+ signalStrength = getString("level")?.toInt() ?: 0,
+ )
+ }
+
+ data class DemoSatelliteEvent(
+ val connectionState: SatelliteConnectionState,
+ val signalStrength: Int,
+ )
+
+ private fun String?.toConnectionState(): SatelliteConnectionState {
+ if (this == null) {
+ return SatelliteConnectionState.Unknown
+ }
+ return try {
+ // Lets people use "connected" on the command line and have it be correctly converted
+ // to [SatelliteConnectionState.Connected] with a capital C.
+ SatelliteConnectionState.valueOf(this.replaceFirstChar { it.uppercase() })
+ } catch (e: IllegalArgumentException) {
+ SatelliteConnectionState.Unknown
+ }
+ }
+
+ private companion object {
+ val DEFAULT_VALUE = DemoSatelliteEvent(SatelliteConnectionState.Unknown, signalStrength = 0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
new file mode 100644
index 0000000..56034f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.data.demo
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/** A satellite repository that represents the latest satellite values sent via demo mode. */
+@SysUISingleton
+class DemoDeviceBasedSatelliteRepository
+@Inject
+constructor(
+ private val dataSource: DemoDeviceBasedSatelliteDataSource,
+ @Application private val scope: CoroutineScope,
+) : DeviceBasedSatelliteRepository {
+ private var demoCommandJob: Job? = null
+
+ override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
+ override val signalStrength = MutableStateFlow(0)
+ override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(true)
+
+ fun startProcessingCommands() {
+ demoCommandJob =
+ scope.launch { dataSource.satelliteEvents.collect { event -> processEvent(event) } }
+ }
+
+ fun stopProcessingCommands() {
+ demoCommandJob?.cancel()
+ }
+
+ private fun processEvent(event: DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent) {
+ connectionState.value = event.connectionState
+ signalStrength.value = event.signalStrength
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 3e3ea85..0739b836 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -31,7 +31,7 @@
import com.android.systemui.log.core.MessageInitializer
import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
-import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported
@@ -50,12 +50,14 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -134,7 +136,7 @@
@Application private val scope: CoroutineScope,
@OemSatelliteInputLog private val logBuffer: LogBuffer,
private val systemClock: SystemClock,
-) : DeviceBasedSatelliteRepository {
+) : RealDeviceBasedSatelliteRepository {
private val satelliteManager: SatelliteManager?
@@ -200,10 +202,12 @@
}
override val connectionState =
- satelliteSupport.whenSupported(
- supported = ::connectionStateFlow,
- orElse = flowOf(SatelliteConnectionState.Off)
- )
+ satelliteSupport
+ .whenSupported(
+ supported = ::connectionStateFlow,
+ orElse = flowOf(SatelliteConnectionState.Off)
+ )
+ .stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off)
// By using the SupportedSatelliteManager here, we expect registration never to fail
private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
@@ -227,7 +231,9 @@
.flowOn(bgDispatcher)
override val signalStrength =
- satelliteSupport.whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0))
+ satelliteSupport
+ .whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0))
+ .stateIn(scope, SharingStarted.Eagerly, 0)
// By using the SupportedSatelliteManager here, we expect registration never to fail
private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 37eda64..9273103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -17,11 +17,11 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
-import android.view.View;
import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -51,23 +51,23 @@
*
* Can pass the view that triggered the request.
*/
- void setPowerSaveMode(boolean powerSave, @Nullable View view);
+ void setPowerSaveMode(boolean powerSave, @Nullable Expandable expandable);
/**
* Gets a reference to the last view used when called {@link #setPowerSaveMode}.
*/
@Nullable
- default WeakReference<View> getLastPowerSaverStartView() {
+ default WeakReference<Expandable> getLastPowerSaverStartExpandable() {
return null;
}
/**
* Clears the last view used when called {@link #setPowerSaveMode}.
*
- * Immediately after calling this, a call to {@link #getLastPowerSaverStartView()} should return
- * {@code null}.
+ * Immediately after calling this, a call to {@link #getLastPowerSaverStartExpandable()} should
+ * return {@code null}.
*/
- default void clearLastPowerSaverStartView() {}
+ default void clearLastPowerSaverStartExpandable() {}
/**
* Returns {@code true} if the device is currently plugged in.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index dab27bb..6012ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -37,7 +37,6 @@
import android.os.PowerManager;
import android.os.PowerSaveState;
import android.util.IndentingPrintWriter;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -48,6 +47,7 @@
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.Dumpable;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -110,9 +110,10 @@
private boolean mFetchingEstimate = false;
// Use AtomicReference because we may request it from a different thread
- // Use WeakReference because we are keeping a reference to a View that's not as long lived
- // as this controller.
- private AtomicReference<WeakReference<View>> mPowerSaverStartView = new AtomicReference<>();
+ // Use WeakReference because we are keeping a reference to an Expandable that's not as long
+ // lived as this controller.
+ private AtomicReference<WeakReference<Expandable>> mPowerSaverStartExpandable =
+ new AtomicReference<>();
@VisibleForTesting
public BatteryControllerImpl(
@@ -196,20 +197,20 @@
}
@Override
- public void setPowerSaveMode(boolean powerSave, View view) {
- if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
+ public void setPowerSaveMode(boolean powerSave, Expandable expandable) {
+ if (powerSave) mPowerSaverStartExpandable.set(new WeakReference<>(expandable));
BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
SAVER_ENABLED_QS);
}
@Override
- public WeakReference<View> getLastPowerSaverStartView() {
- return mPowerSaverStartView.get();
+ public WeakReference<Expandable> getLastPowerSaverStartExpandable() {
+ return mPowerSaverStartExpandable.get();
}
@Override
- public void clearLastPowerSaverStartView() {
- mPowerSaverStartView.set(null);
+ public void clearLastPowerSaverStartExpandable() {
+ mPowerSaverStartExpandable.set(null);
}
@Override
@@ -543,4 +544,4 @@
public boolean isChargingSourceDock() {
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_DOCK;
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
index abc12ed..e9c742d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
@@ -24,7 +24,6 @@
import android.bluetooth.BluetoothDevice;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.View;
import androidx.test.filters.SmallTest;
@@ -34,6 +33,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import org.junit.Before;
@@ -56,9 +56,10 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
- private final View mView = new View(mContext);
private final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<>();
@Mock
+ private Expandable mExpandable;
+ @Mock
private DialogTransitionAnimator mDialogTransitionAnimator;
@Mock
private HearingDevicesDialogDelegate.Factory mDialogFactory;
@@ -97,7 +98,7 @@
public void showDialog_bluetoothDisable_showPairNewDeviceTrue() {
when(mLocalBluetoothAdapter.isEnabled()).thenReturn(false);
- mManager.showDialog(mView);
+ mManager.showDialog(mExpandable);
verify(mDialogFactory).create(eq(true));
}
@@ -109,7 +110,7 @@
when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
mCachedDevices.add(mCachedDevice);
- mManager.showDialog(mView);
+ mManager.showDialog(mExpandable);
verify(mDialogFactory).create(eq(false));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index fbe1184..e64df90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -28,10 +28,13 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.motion.MotionTestRule
import platform.test.motion.RecordedMotion
-import platform.test.motion.Sampling.Companion.evenlySampled
+import platform.test.motion.view.AnimationSampling.Companion.evenlySampled
import platform.test.motion.view.DrawableFeatureCaptures
-import platform.test.motion.view.ViewMotionTestRule
+import platform.test.motion.view.ViewRecordingSpec.Companion.captureWithoutScreenshot
+import platform.test.motion.view.ViewToolkit
+import platform.test.motion.view.record
import platform.test.screenshot.DeviceEmulationRule
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.DisplaySpec
@@ -64,7 +67,8 @@
@get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
@get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
- @get:Rule(order = 2) val motionRule = ViewMotionTestRule(pathManager, { activityRule.scenario })
+ @get:Rule(order = 2)
+ val motionRule = MotionTestRule(ViewToolkit { activityRule.scenario }, pathManager)
@Test
fun backgroundAnimation_whenLaunching() {
@@ -151,15 +155,14 @@
backgroundLayer: GradientDrawable,
animator: AnimatorSet
): RecordedMotion {
- return motionRule.checkThat(animator).record(
- backgroundLayer,
- evenlySampled(20),
- visualCapture = null
- ) {
- capture(DrawableFeatureCaptures.bounds, "bounds")
- capture(DrawableFeatureCaptures.cornerRadii, "corner_radii")
- capture(DrawableFeatureCaptures.alpha, "alpha")
- }
+ return motionRule.record(
+ animator,
+ backgroundLayer.captureWithoutScreenshot(evenlySampled(20)) {
+ feature(DrawableFeatureCaptures.bounds, "bounds")
+ feature(DrawableFeatureCaptures.cornerRadii, "corner_radii")
+ feature(DrawableFeatureCaptures.alpha, "alpha")
+ }
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index b05d959..af1d315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -22,7 +22,6 @@
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
-import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -30,6 +29,7 @@
import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.FakeSharedPreferences
@@ -100,6 +100,8 @@
@Mock private lateinit var bluetoothTileDialogDelegate: BluetoothTileDialogDelegate
@Mock private lateinit var sysuiDialog: SystemUIDialog
+ @Mock private lateinit var expandable: Expandable
+ @Mock private lateinit var controller: DialogTransitionAnimator.Controller
private val sharedPreferences = FakeSharedPreferences()
@@ -157,6 +159,7 @@
.thenReturn(getMutableStateFlow(false))
whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
.thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
+ whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
}
@Test
@@ -164,16 +167,16 @@
testScope.runTest {
bluetoothTileDialogViewModel.showDialog(null)
- verify(mDialogTransitionAnimator, never()).showFromView(any(), any(), any(), any())
+ verify(mDialogTransitionAnimator, never()).show(any(), any(), any())
}
}
@Test
fun testShowDialog_animated() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDialog(LinearLayout(mContext))
+ bluetoothTileDialogViewModel.showDialog(expandable)
- verify(mDialogTransitionAnimator).showFromView(any(), any(), nullable(), anyBoolean())
+ verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
}
}
@@ -181,10 +184,9 @@
fun testShowDialog_animated_callInBackgroundThread() {
testScope.runTest {
backgroundExecutor.execute {
- bluetoothTileDialogViewModel.showDialog(LinearLayout(mContext))
+ bluetoothTileDialogViewModel.showDialog(expandable)
- verify(mDialogTransitionAnimator)
- .showFromView(any(), any(), nullable(), anyBoolean())
+ verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index 39fcd41..5b836b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -346,6 +346,24 @@
}
@Test
+ public void testStartDozing_withMinShowTime() {
+ // GIVEN a biometric message is showing
+ mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ new KeyguardIndication.Builder()
+ .setMessage("test_message")
+ .setMinVisibilityMillis(5000L)
+ .setTextColor(ColorStateList.valueOf(Color.WHITE))
+ .build(),
+ true);
+
+ // WHEN the device wants to hide the biometric message
+ mController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+
+ // THEN switch to INDICATION_TYPE_NONE
+ verify(mView).switchIndication(null);
+ }
+
+ @Test
public void testStoppedDozing() {
// GIVEN we're dozing & we have an indication message
mStatusBarStateListener.onDozingChanged(true);
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 325e7bf..6b1d39a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -241,7 +241,7 @@
.thenReturn(mock(Flow.class));
when(mDreamViewModel.getTransitionEnded())
.thenReturn(mock(Flow.class));
- when(mCommunalTransitionViewModel.getShowByDefault())
+ when(mCommunalTransitionViewModel.getShowCommunalFromOccluded())
.thenReturn(mock(Flow.class));
when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded())
.thenReturn(mock(Flow.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
index bcaad01..f5b5261 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -19,24 +19,20 @@
package com.android.systemui.keyguard.data.repository
-import android.os.fakeExecutorHandler
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
-import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.ThreadAssert
-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
@@ -50,31 +46,32 @@
class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardBlueprintRepository
@Mock lateinit var configurationRepository: ConfigurationRepository
- @Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint
@Mock lateinit var threadAssert: ThreadAssert
+
private val testScope = TestScope(StandardTestDispatcher())
private val kosmos: Kosmos = testKosmos()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- with(kosmos) {
- whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
- underTest =
- KeyguardBlueprintRepository(
- setOf(defaultLockscreenBlueprint),
- fakeExecutorHandler,
- threadAssert,
- )
- }
+ underTest = kosmos.keyguardBlueprintRepository
}
@Test
fun testApplyBlueprint_DefaultLayout() {
testScope.runTest {
val blueprint by collectLastValue(underTest.blueprint)
- underTest.applyBlueprint(defaultLockscreenBlueprint)
- assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
+ underTest.applyBlueprint(DefaultKeyguardBlueprint.DEFAULT)
+ assertThat(blueprint).isEqualTo(kosmos.defaultKeyguardBlueprint)
+ }
+ }
+
+ @Test
+ fun testApplyBlueprint_SplitShadeLayout() {
+ testScope.runTest {
+ val blueprint by collectLastValue(underTest.blueprint)
+ underTest.applyBlueprint(SplitShadeKeyguardBlueprint.ID)
+ assertThat(blueprint).isEqualTo(kosmos.splitShadeBlueprint)
}
}
@@ -83,33 +80,22 @@
testScope.runTest {
val blueprint by collectLastValue(underTest.blueprint)
underTest.refreshBlueprint()
- assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
+ assertThat(blueprint).isEqualTo(kosmos.defaultKeyguardBlueprint)
}
}
@Test
- fun testTransitionToLayout_validId() {
- assertThat(underTest.applyBlueprint(DEFAULT)).isTrue()
+ fun testTransitionToDefaultLayout_validId() {
+ assertThat(underTest.applyBlueprint(DefaultKeyguardBlueprint.DEFAULT)).isTrue()
+ }
+
+ @Test
+ fun testTransitionToSplitShadeLayout_validId() {
+ assertThat(underTest.applyBlueprint(SplitShadeKeyguardBlueprint.ID)).isTrue()
}
@Test
fun testTransitionToLayout_invalidId() {
assertThat(underTest.applyBlueprint("abc")).isFalse()
}
-
- @Test
- fun testTransitionToSameBlueprint_refreshesBlueprint() =
- with(kosmos) {
- testScope.runTest {
- val transition by collectLastValue(underTest.refreshTransition)
- fakeExecutor.runAllReady()
- runCurrent()
-
- underTest.applyBlueprint(defaultLockscreenBlueprint)
- fakeExecutor.runAllReady()
- runCurrent()
-
- assertThat(transition).isNotNull()
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 691d48f..1dc58d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -26,8 +26,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dock.DockManager
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -1229,23 +1229,22 @@
}
@Test
- fun occludedToGlanceableHubWhenDocked() =
+ fun occludedToGlanceableHubWhenInitiallyOnHub() =
testScope.runTest {
- // GIVEN a device on lockscreen
+ // GIVEN a device on lockscreen and communal is available
keyguardRepository.setKeyguardShowing(true)
+ kosmos.setCommunalAvailable(true)
runCurrent()
- // GIVEN a prior transition has run to OCCLUDED
+ // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- // GIVEN device is docked/communal is available
- dockManager.setIsDocked(true)
- dockManager.setDockEvent(DockManager.STATE_DOCKED)
+ // GIVEN on blank scene
val idleTransitionState =
MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
+ ObservableTransitionState.Idle(CommunalScenes.Blank)
)
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
index dbf6a29..8a0613f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
@@ -66,25 +66,19 @@
fun testHelp() {
command().execute(pw, listOf("help"))
verify(pw, atLeastOnce()).println(anyString())
- verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
+ verify(keyguardBlueprintInteractor, never()).transitionOrRefreshBlueprint(anyString())
}
@Test
fun testBlank() {
command().execute(pw, listOf())
verify(pw, atLeastOnce()).println(anyString())
- verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
+ verify(keyguardBlueprintInteractor, never()).transitionOrRefreshBlueprint(anyString())
}
@Test
fun testValidArg() {
command().execute(pw, listOf("fake"))
- verify(keyguardBlueprintInteractor).transitionToBlueprint("fake")
- }
-
- @Test
- fun testValidArg_Int() {
- command().execute(pw, listOf("1"))
- verify(keyguardBlueprintInteractor).transitionToBlueprint(1)
+ verify(keyguardBlueprintInteractor).transitionOrRefreshBlueprint("fake")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 4bb0d47..0bca367 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
@@ -110,6 +110,46 @@
assertThat(isVisible).isTrue()
}
+ @Test
+ fun iconType_fingerprint() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT)
+ }
+
+ @Test
+ fun iconType_locked() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintAuthRepository.setIsRunning(false)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK)
+ }
+
+ @Test
+ fun iconType_unlocked() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(true)
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ fingerprintAuthRepository.setIsRunning(false)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
+ }
+
+ @Test
+ fun iconType_none() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(true)
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
+ }
+
private fun deviceEntryIconTransitionAlpha(alpha: Float) {
deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 9429725..b95d3aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -44,7 +44,6 @@
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.View;
import androidx.test.filters.SmallTest;
@@ -53,6 +52,7 @@
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
@@ -88,7 +88,9 @@
@Mock
private UserTracker mUserTracker;
@Mock
- private View mView;
+ private Expandable mExpandable;
+ @Mock
+ private DialogTransitionAnimator.Controller mController;
@Mock
private SystemUIDialog.Factory mSystemUIDialogFactory;
@Mock
@@ -234,32 +236,31 @@
@Test
public void testDialogStartedFromLauncher_viewVisible() {
- when(mBatteryController.getLastPowerSaverStartView())
- .thenReturn(new WeakReference<>(mView));
- when(mView.isAggregatedVisible()).thenReturn(true);
+ when(mBatteryController.getLastPowerSaverStartExpandable())
+ .thenReturn(new WeakReference<>(mExpandable));
+ when(mExpandable.dialogTransitionController(any())).thenReturn(mController);
Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
intent.putExtras(new Bundle());
mReceiver.onReceive(mContext, intent);
- verify(mDialogTransitionAnimator).showFromView(any(), eq(mView), any());
+ verify(mDialogTransitionAnimator).show(any(), eq(mController));
mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
}
@Test
public void testDialogStartedNotFromLauncher_viewNotVisible() {
- when(mBatteryController.getLastPowerSaverStartView())
- .thenReturn(new WeakReference<>(mView));
- when(mView.isAggregatedVisible()).thenReturn(false);
+ when(mBatteryController.getLastPowerSaverStartExpandable())
+ .thenReturn(new WeakReference<>(mExpandable));
Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
intent.putExtras(new Bundle());
mReceiver.onReceive(mContext, intent);
- verify(mDialogTransitionAnimator, never()).showFromView(any(), any());
+ verify(mDialogTransitionAnimator, never()).show(any(), any());
verify(mPowerNotificationWarnings.getSaverConfirmationDialog()).show();
mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
@@ -267,7 +268,7 @@
@Test
public void testDialogShownNotFromLauncher() {
- when(mBatteryController.getLastPowerSaverStartView()).thenReturn(null);
+ when(mBatteryController.getLastPowerSaverStartExpandable()).thenReturn(null);
Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
intent.putExtras(new Bundle());
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 ef7798e..5e14b1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -43,7 +43,6 @@
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.util.SparseArray;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -51,6 +50,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.CollectionUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -734,7 +734,7 @@
}
@Override
- protected void handleClick(@Nullable View view) {}
+ protected void handleClick(@Nullable Expandable expandable) {}
@Override
protected void handleUpdateState(State state, Object arg) {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index df0ab34..8bf743884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -44,13 +44,13 @@
import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.res.R;
@@ -395,13 +395,13 @@
}
@Override
- public void click(@Nullable View view) {}
+ public void click(@Nullable Expandable expandable) {}
@Override
- public void secondaryClick(@Nullable View view) {}
+ public void secondaryClick(@Nullable Expandable expandable) {}
@Override
- public void longClick(@Nullable View view) {}
+ public void longClick(@Nullable Expandable expandable) {}
@Override
public void userSwitch(int currentUser) {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index ef979d2..a8e9db5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -35,11 +35,10 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.IWindowManager
-import android.view.View
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.animation.view.LaunchableFrameLayout
+import com.android.systemui.animation.Expandable
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
@@ -339,7 +338,7 @@
tile.qsTile.activityLaunchForClick = pi
}
- tile.handleClick(mock(View::class.java))
+ tile.handleClick(mock(Expandable::class.java))
testableLooper.processAllMessages()
verify(activityStarter, never())
@@ -366,7 +365,7 @@
val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.qsTile.activityLaunchForClick = pi
- tile.handleClick(mock(LaunchableFrameLayout::class.java))
+ tile.handleClick(mock(Expandable::class.java))
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 22b1c7b..c706244 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -51,7 +51,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -63,6 +62,7 @@
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.systemui.InstanceIdSequenceFake;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -148,7 +148,7 @@
@Test
public void testClick_Metrics() {
- mTile.click(null /* view */);
+ mTile.click(null /* expandable */);
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
assertEquals(1, mUiEventLoggerFake.numLogs());
UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0);
@@ -159,7 +159,7 @@
public void testClick_log() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- mTile.click(null /* view */);
+ mTile.click(null /* expandable */);
verify(mQsLogger).logTileClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE),
anyInt());
}
@@ -184,7 +184,7 @@
@Test
public void testClick_Metrics_Status_Bar_Status() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- mTile.click(null /* view */);
+ mTile.click(null /* expandable */);
verify(mMetricsLogger).write(mLogCaptor.capture());
assertEquals(StatusBarState.SHADE, mLogCaptor.getValue()
.getTaggedData(FIELD_STATUS_BAR_STATE));
@@ -193,12 +193,12 @@
@Test
public void testClick_falsing() {
mFalsingManager.setFalseTap(true);
- mTile.click(null /* view */);
+ mTile.click(null /* expandable */);
mTestableLooper.processAllMessages();
assertThat(mTile.mClicked).isFalse();
mFalsingManager.setFalseTap(false);
- mTile.click(null /* view */);
+ mTile.click(null /* expandable */);
mTestableLooper.processAllMessages();
assertThat(mTile.mClicked).isTrue();
}
@@ -206,19 +206,19 @@
@Test
public void testLongClick_falsing() {
mFalsingManager.setFalseLongTap(true);
- mTile.longClick(null /* view */);
+ mTile.longClick(null /* expandable */);
mTestableLooper.processAllMessages();
assertThat(mTile.mLongClicked).isFalse();
mFalsingManager.setFalseLongTap(false);
- mTile.longClick(null /* view */);
+ mTile.longClick(null /* expandable */);
mTestableLooper.processAllMessages();
assertThat(mTile.mLongClicked).isTrue();
}
@Test
public void testSecondaryClick_Metrics() {
- mTile.secondaryClick(null /* view */);
+ mTile.secondaryClick(null /* expandable */);
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
assertEquals(1, mUiEventLoggerFake.numLogs());
UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0);
@@ -229,7 +229,7 @@
public void testSecondaryClick_log() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- mTile.secondaryClick(null /* view */);
+ mTile.secondaryClick(null /* expandable */);
verify(mQsLogger).logTileSecondaryClick(eq(SPEC), eq(StatusBarState.SHADE),
eq(Tile.STATE_ACTIVE), anyInt());
}
@@ -254,7 +254,7 @@
@Test
public void testSecondaryClick_Metrics_Status_Bar_Status() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- mTile.secondaryClick(null /* view */);
+ mTile.secondaryClick(null /* expandable */);
verify(mMetricsLogger).write(mLogCaptor.capture());
assertEquals(StatusBarState.KEYGUARD, mLogCaptor.getValue()
.getTaggedData(FIELD_STATUS_BAR_STATE));
@@ -262,7 +262,7 @@
@Test
public void testLongClick_Metrics() {
- mTile.longClick(null /* view */);
+ mTile.longClick(null /* expandable */);
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
assertEquals(1, mUiEventLoggerFake.numLogs());
UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0);
@@ -274,7 +274,7 @@
public void testLongClick_log() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- mTile.longClick(null /* view */);
+ mTile.longClick(null /* expandable */);
verify(mQsLogger).logTileLongClick(eq(SPEC), eq(StatusBarState.SHADE),
eq(Tile.STATE_ACTIVE), anyInt());
}
@@ -299,7 +299,7 @@
@Test
public void testLongClick_Metrics_Status_Bar_Status() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
- mTile.click(null /* view */);
+ mTile.click(null /* expandable */);
verify(mMetricsLogger).write(mLogCaptor.capture());
assertEquals(StatusBarState.SHADE_LOCKED, mLogCaptor.getValue()
.getTaggedData(FIELD_STATUS_BAR_STATE));
@@ -560,12 +560,12 @@
}
@Override
- protected void handleClick(@Nullable View view) {
+ protected void handleClick(@Nullable Expandable expandable) {
mClicked = true;
}
@Override
- protected void handleLongClick(@Nullable View view) {
+ protected void handleLongClick(@Nullable Expandable expandable) {
mLongClicked = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index 605dc14..2c49e92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -21,11 +21,10 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
@@ -34,6 +33,7 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
@@ -59,24 +59,15 @@
private const val USER = 10
}
- @Mock
- private lateinit var userContext: Context
- @Mock
- private lateinit var qsHost: QSHost
- @Mock
- private lateinit var uiEventLogger: QsEventLogger
- @Mock
- private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var batteryController: BatteryController
- @Mock
- private lateinit var view: View
+ @Mock private lateinit var userContext: Context
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var expandable: Expandable
private lateinit var secureSettings: SecureSettings
private lateinit var testableLooper: TestableLooper
private lateinit var tile: BatterySaverTile
@@ -91,7 +82,8 @@
secureSettings = FakeSettings()
- tile = BatterySaverTile(
+ tile =
+ BatterySaverTile(
qsHost,
uiEventLogger,
testableLooper.looper,
@@ -102,7 +94,8 @@
activityStarter,
qsLogger,
batteryController,
- secureSettings)
+ secureSettings
+ )
tile.initialize()
testableLooper.processAllMessages()
@@ -131,23 +124,23 @@
@Test
fun testClickingPowerSavePassesView() {
tile.onPowerSaveChanged(true)
- tile.handleClick(view)
+ tile.handleClick(expandable)
tile.onPowerSaveChanged(false)
- tile.handleClick(view)
+ tile.handleClick(expandable)
- verify(batteryController).setPowerSaveMode(true, view)
- verify(batteryController).setPowerSaveMode(false, view)
+ verify(batteryController).setPowerSaveMode(true, expandable)
+ verify(batteryController).setPowerSaveMode(false, expandable)
}
@Test
fun testStopListeningClearsViewInController() {
clearInvocations(batteryController)
tile.handleSetListening(true)
- verify(batteryController, never()).clearLastPowerSaverStartView()
+ verify(batteryController, never()).clearLastPowerSaverStartExpandable()
tile.handleSetListening(false)
- verify(batteryController).clearLastPowerSaverStartView()
+ verify(batteryController).clearLastPowerSaverStartExpandable()
}
@Test
@@ -158,7 +151,7 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off))
}
@Test
@@ -169,6 +162,6 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on))
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index cca1344..1173fa3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -26,12 +26,12 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.ContextThemeWrapper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
@@ -42,7 +42,6 @@
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
@@ -99,6 +98,12 @@
@Mock
private lateinit var hostDialog: Dialog
+ @Mock
+ private lateinit var expandable: Expandable
+
+ @Mock
+ private lateinit var controller: DialogTransitionAnimator.Controller
+
private lateinit var secureSettings: SecureSettings
private lateinit var testableLooper: TestableLooper
private lateinit var tile: DndTile
@@ -119,6 +124,7 @@
}
}
whenever(qsHost.context).thenReturn(wrappedContext)
+ whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
tile = DndTile(
qsHost,
@@ -187,11 +193,10 @@
secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER)
testableLooper.processAllMessages()
- val view = View(context)
- tile.handleClick(view)
+ tile.handleClick(expandable)
testableLooper.processAllMessages()
- verify(mDialogTransitionAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
+ verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
}
@Test
@@ -201,8 +206,7 @@
secureSettings.putIntForUser(KEY, 60, DEFAULT_USER)
testableLooper.processAllMessages()
- val view = View(context)
- tile.handleClick(view)
+ tile.handleClick(expandable)
testableLooper.processAllMessages()
verify(mDialogTransitionAnimator, never())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index 1f5ebfe..1c42dd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -20,12 +20,12 @@
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -37,7 +37,6 @@
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.After
@@ -67,6 +66,8 @@
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
@Mock private lateinit var dialog: SystemUIDialog
+ @Mock private lateinit var expandable: Expandable
+ @Mock private lateinit var controller: DialogTransitionAnimator.Controller
private lateinit var testableLooper: TestableLooper
private lateinit var systemClock: FakeSystemClock
@@ -81,6 +82,7 @@
testableLooper = TestableLooper.get(this)
`when`(qsHost.getContext()).thenReturn(mContext)
`when`(fontScalingDialogDelegate.createDialog()).thenReturn(dialog)
+ `when`(expandable.dialogTransitionController(any())).thenReturn(controller)
systemClock = FakeSystemClock()
backgroundDelayableExecutor = FakeExecutor(systemClock)
@@ -119,8 +121,7 @@
@Test
fun clickTile_screenUnlocked_showDialogAnimationFromView() {
`when`(keyguardStateController.isShowing).thenReturn(false)
- val view = View(context)
- fontScalingTile.click(view)
+ fontScalingTile.click(expandable)
testableLooper.processAllMessages()
verify(activityStarter)
@@ -132,14 +133,13 @@
eq(false)
)
argumentCaptor.value.run()
- verify(mDialogTransitionAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
+ verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
}
@Test
fun clickTile_onLockScreen_neverShowDialogAnimationFromView() {
`when`(keyguardStateController.isShowing).thenReturn(true)
- val view = View(context)
- fontScalingTile.click(view)
+ fontScalingTile.click(expandable)
testableLooper.processAllMessages()
verify(activityStarter)
@@ -151,8 +151,7 @@
eq(false)
)
argumentCaptor.value.run()
- verify(mDialogTransitionAnimator, never())
- .showFromView(any(), eq(view), nullable(), anyBoolean())
+ verify(mDialogTransitionAnimator, never()).show(any(), any(), anyBoolean())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
index 73aa54c..56671bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -135,10 +136,10 @@
@Test
public void handleClick_dialogShown() {
- View view = new View(mContext);
- mTile.handleClick(view);
+ Expandable expandable = Expandable.fromView(new View(mContext));
+ mTile.handleClick(expandable);
mTestableLooper.processAllMessages();
- verify(mHearingDevicesDialogManager).showDialog(view);
+ verify(mHearingDevicesDialogManager).showDialog(expandable);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 387f27d..effae5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -241,6 +241,7 @@
statusBarWinController,
sysUiState,
mock(),
+ mock(),
userTracker,
wakefulnessLifecycle,
uiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
index 91f3912..5e7d8fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Ignore
import kotlin.test.Test
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -56,7 +55,6 @@
private lateinit var actionExecutor: ActionExecutor
- @Ignore // Fixed with newer mockito version (in main)
@Test
fun startSharedTransition_callsLaunchIntent() = runTest {
actionExecutor = createActionExecutor()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
index bedb2b3..e0eb99c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
@@ -16,120 +16,161 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
+
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.Intent;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.shared.recents.utilities.Utilities;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
+import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
- @Rule public MockitoRule mockito = MockitoJUnit.rule();
+ private static final Intent SHOW_INTENT = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+ private static final Intent DISMISS_INTENT =
+ new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS);
+ private StaticMockitoSession mockitoSession;
private KeyboardShortcutsReceiver mKeyboardShortcutsReceiver;
- private Intent mIntent;
- private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock private KeyboardShortcuts mKeyboardShortcuts;
@Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch;
@Before
public void setUp() {
- mIntent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+ MockitoAnnotations.initMocks(this);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
mKeyboardShortcuts.mContext = mContext;
mKeyboardShortcutListSearch.mContext = mContext;
KeyboardShortcuts.sInstance = mKeyboardShortcuts;
KeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
+
+ mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
+ }
+
+ @Before
+ public void startStaticMocking() {
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(Utilities.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ }
+
+ @After
+ public void endStaticMocking() {
+ mockitoSession.finishMocking();
}
@Test
public void onReceive_whenFlagOffDeviceIsTablet_showKeyboardShortcuts() {
- MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
- .spyStatic(Utilities.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
when(Utilities.isLargeScreen(mContext)).thenReturn(true);
- mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+ mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt());
verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt());
- mockitoSession.finishMocking();
}
@Test
public void onReceive_whenFlagOffDeviceIsNotTablet_showKeyboardShortcuts() {
- MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
- .spyStatic(Utilities.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
when(Utilities.isLargeScreen(mContext)).thenReturn(false);
- mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+ mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt());
verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt());
- mockitoSession.finishMocking();
}
@Test
public void onReceive_whenFlagOnDeviceIsTablet_showKeyboardShortcutListSearch() {
- MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
- .spyStatic(Utilities.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
when(Utilities.isLargeScreen(mContext)).thenReturn(true);
- mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+ mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
verify(mKeyboardShortcuts, never()).showKeyboardShortcuts(anyInt());
verify(mKeyboardShortcutListSearch).showKeyboardShortcuts(anyInt());
- mockitoSession.finishMocking();
}
@Test
public void onReceive_whenFlagOnDeviceIsNotTablet_showKeyboardShortcuts() {
- MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
- .spyStatic(Utilities.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
when(Utilities.isLargeScreen(mContext)).thenReturn(false);
- mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
+ mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt());
verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt());
- mockitoSession.finishMocking();
+ }
+
+ @Test
+ public void onShowIntent_rewriteFlagOn_oldFlagOn_isLargeScreen_doesNotLaunchOldVersions() {
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ when(Utilities.isLargeScreen(mContext)).thenReturn(true);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
+
+ verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void onShowIntent_rewriteFlagOn_oldFlagOn_isSmallScreen_doesNotLaunchOldVersions() {
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ when(Utilities.isLargeScreen(mContext)).thenReturn(false);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
+
+ verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void onDismissIntent_rewriteFlagOn_oldFlagOn_isLargeScreen_doesNotDismissOldVersions() {
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ when(Utilities.isLargeScreen(mContext)).thenReturn(true);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, DISMISS_INTENT);
+
+ verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void onDismissIntent_rewriteFlagOn_oldFlagOn_isSmallScreen_doesNotDismissOldVersions() {
+ mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ when(Utilities.isLargeScreen(mContext)).thenReturn(false);
+
+ mKeyboardShortcutsReceiver.onReceive(mContext, DISMISS_INTENT);
+
+ verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f666d8e..b9312d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -21,9 +21,12 @@
import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
import static android.provider.Settings.Global.HEADS_UP_ON;
+import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
+import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
import static com.google.common.truth.Truth.assertThat;
@@ -42,6 +45,7 @@
import static org.mockito.Mockito.spy;
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 static java.util.Collections.emptySet;
@@ -52,6 +56,8 @@
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.graphics.Rect;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -73,6 +79,8 @@
import android.util.SparseArray;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.test.filters.SmallTest;
@@ -137,6 +145,8 @@
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.KeyboardShortcutListSearch;
+import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -202,6 +212,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.ByteArrayOutputStream;
@@ -326,18 +337,21 @@
@Mock IPowerManager mPowerManagerService;
@Mock ActivityStarter mActivityStarter;
@Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+ @Mock private KeyboardShortcuts mKeyboardShortcuts;
+ @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
private final SystemSettings mSystemSettings = new FakeSettings();
private final FakeEventLog mFakeEventLog = new FakeEventLog();
- private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+ private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private final InitController mInitController = new InitController();
private final DumpManager mDumpManager = new DumpManager();
private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
+ private MessageRouterImpl mMessageRouter = new MessageRouterImpl(mMainExecutor);
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
mKosmos.getBrightnessMirrorShowingInteractor();
@@ -347,7 +361,7 @@
MockitoAnnotations.initMocks(this);
// Set default value to avoid IllegalStateException.
- mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
// Turn AOD on and toggle feature flag for jank fixes
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
@@ -462,6 +476,16 @@
}
private void createCentralSurfaces() {
+ mMainExecutor = new FakeExecutor(mFakeSystemClock);
+ mMessageRouter = new MessageRouterImpl(mMainExecutor);
+ mKeyboardShortcuts = mock(KeyboardShortcuts.class);
+ mKeyboardShortcutListSearch = mock(KeyboardShortcutListSearch.class);
+ // Test setup for legacy version
+ mKeyboardShortcuts.mContext = mContext;
+ mKeyboardShortcutListSearch.mContext = mContext;
+ KeyboardShortcuts.sInstance = mKeyboardShortcuts;
+ KeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
+
ConfigurationController configurationController = new ConfigurationControllerImpl(mContext);
mCentralSurfaces = new CentralSurfacesImpl(
mContext,
@@ -554,7 +578,7 @@
mFeatureFlags,
mKeyguardUnlockAnimationController,
mMainExecutor,
- new MessageRouterImpl(mMainExecutor),
+ mMessageRouter,
mWallpaperManager,
Optional.of(mStartingSurface),
mActivityTransitionAnimator,
@@ -1126,6 +1150,194 @@
verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any());
}
+ @Test
+ public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() {
+ switchToLargeScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ dismissKeyboardShortcuts();
+
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() {
+ switchToLargeScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ dismissKeyboardShortcuts();
+
+ verify(mKeyboardShortcutListSearch).dismissKeyboardShortcuts();
+ }
+
+ @Test
+ public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() {
+ switchToLargeScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ dismissKeyboardShortcuts();
+
+ verify(mKeyboardShortcuts).dismissKeyboardShortcuts();
+ verifyNoMoreInteractions(mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() {
+ switchToSmallScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ dismissKeyboardShortcuts();
+
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() {
+ switchToSmallScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ dismissKeyboardShortcuts();
+
+ verify(mKeyboardShortcuts).dismissKeyboardShortcuts();
+ verifyNoMoreInteractions(mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() {
+ switchToSmallScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ dismissKeyboardShortcuts();
+
+ verify(mKeyboardShortcuts).dismissKeyboardShortcuts();
+ verifyNoMoreInteractions(mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() {
+ switchToLargeScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ int deviceId = 321;
+ toggleKeyboardShortcuts(/* deviceId= */ deviceId);
+
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() {
+ switchToLargeScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ int deviceId = 654;
+ toggleKeyboardShortcuts(deviceId);
+
+ verify(mKeyboardShortcutListSearch).showKeyboardShortcuts(deviceId);
+ verifyNoMoreInteractions(mKeyboardShortcuts);
+ }
+
+ @Test
+ public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() {
+ switchToLargeScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ int deviceId = 987;
+ toggleKeyboardShortcuts(deviceId);
+
+ verify(mKeyboardShortcuts).showKeyboardShortcuts(deviceId);
+ verifyNoMoreInteractions(mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() {
+ switchToSmallScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ int deviceId = 789;
+ toggleKeyboardShortcuts(/* deviceId= */ deviceId);
+
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() {
+ switchToSmallScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ int deviceId = 456;
+ toggleKeyboardShortcuts(deviceId);
+
+ verify(mKeyboardShortcuts).showKeyboardShortcuts(deviceId);
+ verifyNoMoreInteractions(mKeyboardShortcutListSearch);
+ }
+
+ @Test
+ public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() {
+ switchToSmallScreen();
+ mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
+ createCentralSurfaces();
+
+ int deviceId = 123;
+ toggleKeyboardShortcuts(deviceId);
+
+ verify(mKeyboardShortcuts).showKeyboardShortcuts(deviceId);
+ verifyNoMoreInteractions(mKeyboardShortcutListSearch);
+ }
+
+ private void dismissKeyboardShortcuts() {
+ mMessageRouter.sendMessage(MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU);
+ mMainExecutor.runAllReady();
+ }
+
+ private void toggleKeyboardShortcuts(int deviceId) {
+ mMessageRouter.sendMessage(new CentralSurfaces.KeyboardShortcutsMessage(deviceId));
+ mMainExecutor.runAllReady();
+ }
+
+ private void switchToLargeScreen() {
+ switchToScreenSize(1280, 800);
+ }
+
+ private void switchToSmallScreen() {
+ switchToScreenSize(504, 1122);
+ }
+
+ private void switchToScreenSize(int widthDp, int heightDp) {
+ WindowMetrics windowMetrics = Mockito.mock(WindowMetrics.class);
+ WindowManager windowManager = Mockito.mock(WindowManager.class);
+
+ Configuration configuration = new Configuration();
+ configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+ mContext.getOrCreateTestableResources().overrideConfiguration(configuration);
+
+ when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, widthDp, heightDp));
+ when(windowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
+ mContext.addMockSystemService(WindowManager.class, windowManager);
+ }
+
/**
* Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
* to reconfigure the keyguard to reflect the requested showing/occluded states.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
new file mode 100644
index 0000000..7ca3b1c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.data
+
+import android.telephony.satellite.SatelliteManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteDataSource
+import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val demoModeController =
+ mock<DemoModeController>().apply { whenever(this.isInDemoMode).thenReturn(false) }
+ private val satelliteManager = mock<SatelliteManager>()
+ private val systemClock = FakeSystemClock()
+
+ private val realImpl =
+ DeviceBasedSatelliteRepositoryImpl(
+ Optional.of(satelliteManager),
+ testDispatcher,
+ testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
+ systemClock,
+ )
+ private val demoDataSource =
+ mock<DemoDeviceBasedSatelliteDataSource>().also {
+ whenever(it.satelliteEvents)
+ .thenReturn(
+ MutableStateFlow(
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Unknown,
+ signalStrength = 0,
+ )
+ )
+ )
+ }
+ private val demoImpl =
+ DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+
+ private val underTest =
+ DeviceBasedSatelliteRepositorySwitcher(
+ realImpl,
+ demoImpl,
+ demoModeController,
+ testScope.backgroundScope,
+ )
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun switcherActiveRepo_updatesWhenDemoModeChanges() =
+ testScope.runTest {
+ assertThat(underTest.activeRepo.value).isSameInstanceAs(realImpl)
+
+ val latest by collectLastValue(underTest.activeRepo)
+ runCurrent()
+
+ startDemoMode()
+
+ assertThat(latest).isSameInstanceAs(demoImpl)
+
+ finishDemoMode()
+
+ assertThat(latest).isSameInstanceAs(realImpl)
+ }
+
+ private fun startDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(true)
+ getDemoModeCallback().onDemoModeStarted()
+ }
+
+ private fun finishDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+ getDemoModeCallback().onDemoModeFinished()
+ }
+
+ private fun getDemoModeCallback(): DemoMode {
+ val captor = kotlinArgumentCaptor<DemoMode>()
+ verify(demoModeController).addCallback(captor.capture())
+ return captor.value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
new file mode 100644
index 0000000..f77fd19
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.data.demo
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+
+@SmallTest
+class DemoDeviceBasedSatelliteRepositoryTest : SysuiTestCase() {
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val fakeSatelliteEvents =
+ MutableStateFlow(
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Unknown,
+ signalStrength = 0,
+ )
+ )
+
+ private lateinit var dataSource: DemoDeviceBasedSatelliteDataSource
+
+ private lateinit var underTest: DemoDeviceBasedSatelliteRepository
+
+ @Before
+ fun setUp() {
+ dataSource =
+ mock<DemoDeviceBasedSatelliteDataSource>().also {
+ whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
+ }
+
+ underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+ }
+
+ @Test
+ fun startProcessing_getsNewUpdates() =
+ testScope.runTest {
+ val latestConnection by collectLastValue(underTest.connectionState)
+ val latestSignalStrength by collectLastValue(underTest.signalStrength)
+
+ underTest.startProcessingCommands()
+
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.On,
+ signalStrength = 3,
+ )
+
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On)
+ assertThat(latestSignalStrength).isEqualTo(3)
+
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Connected,
+ signalStrength = 4,
+ )
+
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.Connected)
+ assertThat(latestSignalStrength).isEqualTo(4)
+ }
+
+ @Test
+ fun stopProcessing_stopsGettingUpdates() =
+ testScope.runTest {
+ val latestConnection by collectLastValue(underTest.connectionState)
+ val latestSignalStrength by collectLastValue(underTest.signalStrength)
+
+ underTest.startProcessingCommands()
+
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.On,
+ signalStrength = 3,
+ )
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On)
+ assertThat(latestSignalStrength).isEqualTo(3)
+
+ underTest.stopProcessingCommands()
+
+ // WHEN new values are emitted
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Connected,
+ signalStrength = 4,
+ )
+
+ // THEN they're not collected because we stopped processing commands, so the old values
+ // are still present
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On)
+ assertThat(latestSignalStrength).isEqualTo(3)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 77e48bff..6b0ad4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -156,7 +156,7 @@
verify(satelliteManager).registerForNtnSignalStrengthChanged(any(), capture())
}
- assertThat(latest).isNull()
+ assertThat(latest).isEqualTo(0)
callback.onNtnSignalStrengthChanged(NtnSignalStrength(1))
assertThat(latest).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 9d4f1fc..ed8843b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -38,13 +38,13 @@
import android.os.PowerSaveState;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticInOrder;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
@@ -72,7 +72,7 @@
@Mock private PowerManager mPowerManager;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private DemoModeController mDemoModeController;
- @Mock private View mView;
+ @Mock private Expandable mExpandable;
@Mock private UsbPort mUsbPort;
@Mock private UsbManager mUsbManager;
@Mock private UsbPortStatus mUsbPortStatus;
@@ -175,8 +175,8 @@
@Test
public void testBatteryUtilsCalledOnSetPowerSaveMode() {
- mBatteryController.setPowerSaveMode(true, mView);
- mBatteryController.setPowerSaveMode(false, mView);
+ mBatteryController.setPowerSaveMode(true, mExpandable);
+ mBatteryController.setPowerSaveMode(false, mExpandable);
StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
@@ -187,21 +187,21 @@
@Test
public void testSaveViewReferenceWhenSettingPowerSaveMode() {
- mBatteryController.setPowerSaveMode(false, mView);
+ mBatteryController.setPowerSaveMode(false, mExpandable);
- Assert.assertNull(mBatteryController.getLastPowerSaverStartView());
+ Assert.assertNull(mBatteryController.getLastPowerSaverStartExpandable());
- mBatteryController.setPowerSaveMode(true, mView);
+ mBatteryController.setPowerSaveMode(true, mExpandable);
- Assert.assertSame(mView, mBatteryController.getLastPowerSaverStartView().get());
+ Assert.assertSame(mExpandable, mBatteryController.getLastPowerSaverStartExpandable().get());
}
@Test
public void testClearViewReference() {
- mBatteryController.setPowerSaveMode(true, mView);
- mBatteryController.clearLastPowerSaverStartView();
+ mBatteryController.setPowerSaveMode(true, mExpandable);
+ mBatteryController.clearLastPowerSaverStartExpandable();
- Assert.assertNull(mBatteryController.getLastPowerSaverStartView());
+ Assert.assertNull(mBatteryController.getLastPowerSaverStartExpandable());
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 3dd382f..3fe6973 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -23,14 +23,15 @@
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -42,6 +43,7 @@
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
applicationScope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
@@ -49,13 +51,13 @@
mediaRepository = communalMediaRepository,
smartspaceRepository = smartspaceRepository,
keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
communalSettingsInteractor = communalSettingsInteractor,
appWidgetHost = mock(),
editWidgetsActivityStarter = editWidgetsActivityStarter,
userTracker = userTracker,
activityStarter = activityStarter,
userManager = userManager,
- dockManager = fakeDockManager,
sceneInteractor = sceneInteractor,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index 0307c41..c4bf8ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -18,7 +18,7 @@
import android.app.PendingIntent
import android.content.Intent
-import android.view.View
+import com.android.systemui.animation.Expandable
/**
* Fake implementation of [QSTileIntentUserInputHandler] interface. Consider using this alongside
@@ -31,22 +31,24 @@
private val mutableInputs = mutableListOf<Input>()
- override fun handle(view: View?, intent: Intent) {
- mutableInputs.add(Input.Intent(view, intent))
+ override fun handle(expandable: Expandable?, intent: Intent) {
+ mutableInputs.add(Input.Intent(expandable, intent))
}
override fun handle(
- view: View?,
+ expandable: Expandable?,
pendingIntent: PendingIntent,
requestLaunchingDefaultActivity: Boolean
) {
- mutableInputs.add(Input.PendingIntent(view, pendingIntent, requestLaunchingDefaultActivity))
+ mutableInputs.add(
+ Input.PendingIntent(expandable, pendingIntent, requestLaunchingDefaultActivity)
+ )
}
sealed interface Input {
- data class Intent(val view: View?, val intent: android.content.Intent) : Input
+ data class Intent(val expandable: Expandable?, val intent: android.content.Intent) : Input
data class PendingIntent(
- val view: View?,
+ val expandable: Expandable?,
val pendingIntent: android.app.PendingIntent,
val requestLaunchingDefaultActivity: Boolean
) : Input
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt
index 832b07a..9cb76bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.tiles.base.interactor
import android.os.UserHandle
-import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
object QSTileInputTestKtx {
@@ -25,12 +25,12 @@
fun <T> click(
data: T,
user: UserHandle = UserHandle.CURRENT,
- view: View? = null,
- ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.Click(view), data)
+ expandable: Expandable? = null,
+ ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.Click(expandable), data)
fun <T> longClick(
data: T,
user: UserHandle = UserHandle.CURRENT,
- view: View? = null,
- ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.LongClick(view), data)
+ expandable: Expandable? = null,
+ ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.LongClick(expandable), data)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index a654d6f..e0f60e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -21,7 +21,6 @@
import com.android.systemui.settings.brightness.MirrorController
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
@@ -30,8 +29,16 @@
override val qqsHeight: Int = 0,
override val qsHeight: Int = 0,
) : QSSceneAdapter {
+ private val _customizerState = MutableStateFlow<CustomizerState>(CustomizerState.Hidden)
+
+ private val _customizerShowing = MutableStateFlow(false)
+ override val isCustomizerShowing = _customizerShowing.asStateFlow()
+
private val _customizing = MutableStateFlow(false)
- override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow()
+ override val isCustomizing = _customizing.asStateFlow()
+
+ private val _animationDuration = MutableStateFlow(0)
+ override val customizerAnimationDuration = _animationDuration.asStateFlow()
private val _view = MutableStateFlow<View?>(null)
override val qsView: Flow<View> = _view.filterNotNull()
@@ -58,7 +65,7 @@
}
fun setCustomizing(value: Boolean) {
- _customizing.value = value
+ updateCustomizerFlows(if (value) CustomizerState.Showing else CustomizerState.Hidden)
}
override suspend fun applyBottomNavBarPadding(padding: Int) {
@@ -66,10 +73,18 @@
}
override fun requestCloseCustomizer() {
- _customizing.value = false
+ updateCustomizerFlows(CustomizerState.Hidden)
}
override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
brightnessMirrorController = mirrorController
}
+
+ private fun updateCustomizerFlows(customizerState: CustomizerState) {
+ _customizerState.value = customizerState
+ _customizing.value = customizerState.isCustomizing
+ _customizerShowing.value = customizerState.isShowing
+ _animationDuration.value =
+ (customizerState as? CustomizerState.Animating)?.animationDuration?.toInt() ?: 0
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
new file mode 100644
index 0000000..5e71227
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
+ Kosmos.Fixture {
+ NotificationsShadeSceneViewModel(
+ applicationScope = applicationCoroutineScope,
+ overlayShadeViewModel = overlayShadeViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index d798b3b..0364e05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -16,8 +16,8 @@
import android.os.Bundle;
import android.testing.LeakCheck;
-import android.view.View;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -61,7 +61,7 @@
* Note: this method ignores the View argument
*/
@Override
- public void setPowerSaveMode(boolean powerSave, View view) {
+ public void setPowerSaveMode(boolean powerSave, Expandable expandable) {
setPowerSaveMode(powerSave);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index de928b5..726a01c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1024,6 +1024,12 @@
intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
}
+ } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+ restoreAccessibilityQsTargets(
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
index 28e8e30..4f95893 100644
--- a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
@@ -34,6 +34,7 @@
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.IntDef;
+import android.os.SystemClock;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -45,7 +46,7 @@
/**
* Helper class to log Autofill Save event stats.
*/
-public final class SaveEventLogger {
+public class SaveEventLogger {
private static final String TAG = "SaveEventLogger";
/**
@@ -112,19 +113,21 @@
public static final int NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG =
AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG;
+ public static final long UNINITIATED_TIMESTAMP = Long.MIN_VALUE;
+
private final int mSessionId;
private Optional<SaveEventInternal> mEventInternal;
+ private long mSessionStartTimestamp;
- private SaveEventLogger(int sessionId) {
- mSessionId = sessionId;
- mEventInternal = Optional.of(new SaveEventInternal());
+ private SaveEventLogger(int sessionId, long sessionStartTimestamp) {
+ mSessionId = sessionId;
+ mEventInternal = Optional.of(new SaveEventInternal());
+ mSessionStartTimestamp = sessionStartTimestamp;
}
- /**
- * A factory constructor to create FillRequestEventLogger.
- */
- public static SaveEventLogger forSessionId(int sessionId) {
- return new SaveEventLogger(sessionId);
+ /** A factory constructor to create FillRequestEventLogger. */
+ public static SaveEventLogger forSessionId(int sessionId, long sessionStartTimestamp) {
+ return new SaveEventLogger(sessionId, sessionStartTimestamp);
}
/**
@@ -224,6 +227,15 @@
});
}
+ /* Returns timestamp (relative to mSessionStartTimestamp) or UNINITIATED_TIMESTAMP if mSessionStartTimestamp is not set */
+ private long getCurrentTimestamp() {
+ long timestamp = UNINITIATED_TIMESTAMP;
+ if (mSessionStartTimestamp != UNINITIATED_TIMESTAMP) {
+ timestamp = SystemClock.elapsedRealtime() - mSessionStartTimestamp;
+ }
+ return timestamp;
+ }
+
/**
* Set latency_save_ui_display_millis as long as mEventInternal presents.
*/
@@ -233,6 +245,11 @@
});
}
+ /** Set latency_save_ui_display_millis as long as mEventInternal presents. */
+ public void maybeSetLatencySaveUiDisplayMillis() {
+ maybeSetLatencySaveUiDisplayMillis(getCurrentTimestamp());
+ }
+
/**
* Set latency_save_request_millis as long as mEventInternal presents.
*/
@@ -242,6 +259,11 @@
});
}
+ /** Set latency_save_request_millis as long as mEventInternal presents. */
+ public void maybeSetLatencySaveRequestMillis() {
+ maybeSetLatencySaveRequestMillis(getCurrentTimestamp());
+ }
+
/**
* Set latency_save_finish_millis as long as mEventInternal presents.
*/
@@ -251,6 +273,11 @@
});
}
+ /** Set latency_save_finish_millis as long as mEventInternal presents. */
+ public void maybeSetLatencySaveFinishMillis() {
+ maybeSetLatencySaveFinishMillis(getCurrentTimestamp());
+ }
+
/**
* Set is_framework_created_save_info as long as mEventInternal presents.
*/
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index cd1ef88..ba6b067 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1531,7 +1531,7 @@
mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId);
mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId);
mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
- mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
+ mSaveEventLogger = SaveEventLogger.forSessionId(sessionId, mLatencyBaseTime);
mIsPrimaryCredential = isPrimaryCredential;
mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
@@ -3931,13 +3931,10 @@
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NONE);
}
- final long saveUiDisplayStartTimestamp = SystemClock.elapsedRealtime();
getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
mService.getServicePackageName(), saveInfo, this,
mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode,
response.getShowSaveDialogIcon(), mSaveEventLogger);
- mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis(
- SystemClock.elapsedRealtime()- saveUiDisplayStartTimestamp);
if (client != null) {
try {
client.setSaveUiState(id, true);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 602855d..3b9c54f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -413,6 +413,8 @@
callback.startIntentSender(intentSender, intent);
}
}, mUiModeMgr.isNightMode(), isUpdate, compatMode, showServiceIcon);
+
+ mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis();
});
}
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 64bca33..04edb57 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -27,6 +27,7 @@
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION;
+import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -210,6 +211,12 @@
}
};
+ private final OnWindowRemovedListener mOnWindowRemovedListener = token -> {
+ synchronized (mSensitiveContentProtectionLock) {
+ mPackagesShowingSensitiveContent.removeIf(pkgInfo -> pkgInfo.getWindowToken() == token);
+ }
+ };
+
public SensitiveContentProtectionManagerService(@NonNull Context context) {
super(context);
if (sensitiveNotificationAppProtection()) {
@@ -265,6 +272,10 @@
// Intra-process call, should never happen.
}
}
+
+ if (sensitiveContentAppProtection()) {
+ mWindowManager.registerOnWindowRemovedListener(mOnWindowRemovedListener);
+ }
}
/** Cleanup any callbacks and listeners */
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c372b3f..4ca9e33 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2415,7 +2415,9 @@
!= ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
// Calling startForeground on a FGS type which has a time limit will only be
// allowed if the app is in a state where it can normally start another FGS
- // and it hasn't hit the time limit for that type in the past 24hrs.
+ // and it hasn't hit its time limit in the past 24hrs, or it has been in the
+ // foreground after it hit its time limit, or it is currently in the
+ // TOP (or better) proc state.
// See if the app could start an FGS or not.
r.clearFgsAllowStart();
@@ -2441,11 +2443,13 @@
SystemClock.elapsedRealtime() - (24 * 60 * 60 * 1000));
final long lastTimeOutAt = fgsTypeInfo.getTimeLimitExceededAt();
if (fgsTypeInfo.getFirstFgsStartRealtime() < before24Hr
+ || r.app.mState.getCurProcState() <= PROCESS_STATE_TOP
|| (lastTimeOutAt != Long.MIN_VALUE
&& r.app.mState.getLastTopTime() > lastTimeOutAt)) {
// Reset the time limit info for this fgs type if it has been
- // more than 24hrs since the first fgs start or if the app was
- // in the TOP state after time limit was exhausted.
+ // more than 24hrs since the first fgs start or if the app is
+ // currently in the TOP state or was in the TOP state after
+ // the time limit was exhausted previously.
fgsTypeInfo.reset();
} else if (lastTimeOutAt > 0) {
// Time limit was exhausted within the past 24 hours and the app
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1b59c18..1b3b198 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4342,6 +4342,7 @@
mServices.bringDownDisabledPackageServicesLocked(
packageName, null, userId, false, true, true);
+ mServices.onUidRemovedLocked(uid);
if (mBooted) {
mAtmInternal.resumeTopActivities(true);
@@ -4372,9 +4373,10 @@
Slog.w(TAG, "Can't force stop all processes of all users, that is insane!");
}
+ final int uid = getPackageManagerInternal().getPackageUid(packageName,
+ MATCH_DEBUG_TRIAGED_MISSING | MATCH_ANY_USER, UserHandle.USER_SYSTEM);
if (appId < 0 && packageName != null) {
- appId = UserHandle.getAppId(getPackageManagerInternal().getPackageUid(packageName,
- MATCH_DEBUG_TRIAGED_MISSING | MATCH_ANY_USER, UserHandle.USER_SYSTEM));
+ appId = UserHandle.getAppId(uid);
}
boolean didSomething;
@@ -4418,6 +4420,7 @@
}
didSomething = true;
}
+ mServices.onUidRemovedLocked(uid);
if (packageName == null) {
// Remove all sticky broadcasts from this user.
@@ -19950,6 +19953,20 @@
return !ActivityManagerService.this.mThemeOverlayReadyUsers.contains(userId);
}
}
+
+ @Override
+ public void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid,
+ int userId) {
+ // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+ if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+ throw new IllegalArgumentException("Unsupported userId");
+ }
+
+ mUserController.handleIncomingUser(pid, uid, userId, true,
+ ALLOW_NON_FULL, "addStartInfoTimestampSystem", null);
+
+ addStartInfoTimestampInternal(key, timestampNs, userId, uid);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -20266,7 +20283,7 @@
final int userId = UserHandle.getCallingUserId();
final long callingId = Binder.clearCallingIdentity();
try {
- if (uid == -1) {
+ if (uid == INVALID_UID) {
uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
}
mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index c5cad14..f5f1928 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2387,8 +2387,8 @@
// Limit the length of the free-form subReason string
if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) {
+ Slog.e(TAG, "subReason is too long, truncating " + subReason);
subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH);
- Slog.e(TAG, "Subreason is too long, truncating: " + subReason);
}
// Log the restriction reason
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index ce41079..e066c23 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -572,12 +572,8 @@
packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
/* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
- } else {
- mIActivityManager.noteAppRestrictionEnabled(
- packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
- false, ActivityManager.RESTRICTION_REASON_USAGE, null,
- 0L);
}
+ // No need to log the unhibernate case as an unstop is logged already in ActivityMS
} catch (RemoteException e) {
Slog.e(TAG, "Couldn't set restriction state change");
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index da528a2..475334c 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -274,8 +274,11 @@
}
/*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
- mBluetoothA2dpEnabled.set(on);
- sendLMsgNoDelay(MSG_L_SET_FORCE_BT_A2DP_USE, SENDMSG_REPLACE, source);
+ boolean wasOn = mBluetoothA2dpEnabled.getAndSet(on);
+ // do not mute music if we do not anticipate a change in A2DP ON state
+ sendLMsgNoDelay(wasOn == on
+ ? MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE : MSG_L_SET_FORCE_BT_A2DP_USE,
+ SENDMSG_REPLACE, source);
}
/**
@@ -1803,6 +1806,7 @@
onSetForceUse(msg.arg1, msg.arg2, false, (String) msg.obj);
break;
case MSG_L_SET_FORCE_BT_A2DP_USE:
+ case MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE:
int forcedUsage = mBluetoothA2dpEnabled.get()
? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
onSetForceUse(AudioSystem.FOR_MEDIA, forcedUsage, true, (String) msg.obj);
@@ -2139,8 +2143,7 @@
private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57;
private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58;
private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59;
-
-
+ private static final int MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE = 60;
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 3e23f97..b709174 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -63,6 +64,7 @@
/** Time in milliseconds that the IME service has to bind before it is reconnected. */
static final long TIME_TO_RECONNECT = 3 * 1000;
+ @UserIdInt final int mUserId;
@NonNull private final InputMethodManagerService mService;
@NonNull private final Context mContext;
@NonNull private final PackageManagerInternal mPackageManagerInternal;
@@ -107,12 +109,15 @@
| Context.BIND_INCLUDE_CAPABILITIES
| Context.BIND_SHOWING_UI;
- InputMethodBindingController(@NonNull InputMethodManagerService service) {
- this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
+ InputMethodBindingController(@UserIdInt int userId,
+ @NonNull InputMethodManagerService service) {
+ this(userId, service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
}
- InputMethodBindingController(@NonNull InputMethodManagerService service,
- int imeConnectionBindFlags, CountDownLatch latchForTesting) {
+ InputMethodBindingController(@UserIdInt int userId,
+ @NonNull InputMethodManagerService service, int imeConnectionBindFlags,
+ CountDownLatch latchForTesting) {
+ mUserId = userId;
mService = service;
mContext = mService.mContext;
mPackageManagerInternal = mService.mPackageManagerInternal;
@@ -301,7 +306,8 @@
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
final InputMethodInfo info =
- mService.queryInputMethodForCurrentUserLocked(mSelectedMethodId);
+ InputMethodSettingsRepository.get(mUserId).getMethodMap().get(
+ mSelectedMethodId);
boolean supportsStylusHwChanged =
mSupportsStylusHw != info.supportsStylusHandwriting();
mSupportsStylusHw = info.supportsStylusHandwriting();
@@ -339,7 +345,7 @@
private void updateCurrentMethodUid() {
final String curMethodPackage = mCurIntent.getComponent().getPackageName();
final int curMethodUid = mPackageManagerInternal.getPackageUid(
- curMethodPackage, 0 /* flags */, mService.getCurrentImeUserIdLocked());
+ curMethodPackage, 0 /* flags */, mUserId);
if (curMethodUid < 0) {
Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
mCurMethodUid = Process.INVALID_UID;
@@ -425,7 +431,8 @@
return InputBindResult.NO_IME;
}
- InputMethodInfo info = mService.queryInputMethodForCurrentUserLocked(mSelectedMethodId);
+ InputMethodInfo info = InputMethodSettingsRepository.get(mUserId).getMethodMap().get(
+ mSelectedMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
}
@@ -497,8 +504,7 @@
Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
return false;
}
- return mContext.bindServiceAsUser(mCurIntent, conn, flags,
- new UserHandle(mService.getCurrentImeUserIdLocked()));
+ return mContext.bindServiceAsUser(mCurIntent, conn, flags, new UserHandle(mUserId));
}
@GuardedBy("ImfLock.class")
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8985022e..14d04119 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -205,6 +205,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
+import java.util.function.IntFunction;
/**
* This class provides a system service that manages input methods.
@@ -306,8 +307,6 @@
@MultiUserUnawareField
private final InputMethodMenuController mMenuController;
@MultiUserUnawareField
- @NonNull private final InputMethodBindingController mBindingController;
- @MultiUserUnawareField
@NonNull private final AutofillSuggestionsController mAutofillController;
@GuardedBy("ImfLock.class")
@@ -478,7 +477,8 @@
@GuardedBy("ImfLock.class")
@Nullable
String getSelectedMethodIdLocked() {
- return mBindingController.getSelectedMethodId();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getSelectedMethodId();
}
/**
@@ -487,7 +487,8 @@
*/
@GuardedBy("ImfLock.class")
private int getSequenceNumberLocked() {
- return mBindingController.getSequenceNumber();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getSequenceNumber();
}
/**
@@ -496,7 +497,8 @@
*/
@GuardedBy("ImfLock.class")
private void advanceSequenceNumberLocked() {
- mBindingController.advanceSequenceNumber();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.advanceSequenceNumber();
}
@GuardedBy("ImfLock.class")
@@ -556,7 +558,8 @@
@GuardedBy("ImfLock.class")
@Nullable
private String getCurIdLocked() {
- return mBindingController.getCurId();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurId();
}
/**
@@ -580,7 +583,8 @@
*/
@GuardedBy("ImfLock.class")
private boolean hasConnectionLocked() {
- return mBindingController.hasMainConnection();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.hasMainConnection();
}
/**
@@ -603,7 +607,8 @@
@GuardedBy("ImfLock.class")
@Nullable
private Intent getCurIntentLocked() {
- return mBindingController.getCurIntent();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurIntent();
}
/**
@@ -613,7 +618,8 @@
@GuardedBy("ImfLock.class")
@Nullable
IBinder getCurTokenLocked() {
- return mBindingController.getCurToken();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurToken();
}
/**
@@ -654,7 +660,8 @@
@GuardedBy("ImfLock.class")
@Nullable
IInputMethodInvoker getCurMethodLocked() {
- return mBindingController.getCurMethod();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurMethod();
}
/**
@@ -662,7 +669,8 @@
*/
@GuardedBy("ImfLock.class")
private int getCurMethodUidLocked() {
- return mBindingController.getCurMethodUid();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurMethodUid();
}
/**
@@ -671,7 +679,8 @@
*/
@GuardedBy("ImfLock.class")
private long getLastBindTimeLocked() {
- return mBindingController.getLastBindTime();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getLastBindTime();
}
/**
@@ -1353,7 +1362,7 @@
InputMethodManagerService(
Context context,
@Nullable ServiceThread serviceThreadForTesting,
- @Nullable InputMethodBindingController bindingControllerForTesting) {
+ @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
synchronized (ImfLock.class) {
mContext = context;
mRes = context.getResources();
@@ -1392,7 +1401,12 @@
AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
- mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal);
+ @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
+ bindingControllerFactory = userId -> new InputMethodBindingController(userId,
+ InputMethodManagerService.this);
+ mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
+ bindingControllerForTesting != null ? bindingControllerForTesting
+ : bindingControllerFactory);
for (int id : mUserManagerInternal.getUserIds()) {
mUserDataRepository.getOrCreate(id);
}
@@ -1406,12 +1420,7 @@
new HardwareKeyboardShortcutController(settings.getMethodMap(),
settings.getUserId());
mMenuController = new InputMethodMenuController(this);
- mBindingController =
- bindingControllerForTesting != null
- ? bindingControllerForTesting
- : new InputMethodBindingController(this);
mAutofillController = new AutofillSuggestionsController(this);
-
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -1544,9 +1553,9 @@
// Note that in b/197848765 we want to see if we can keep the binding alive for better
// profile switching.
- mBindingController.unbindCurrentMethod();
- // TODO(b/325515685): No need to do this once BindingController becomes per-user.
- mBindingController.setSelectedMethodId(null);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.unbindCurrentMethod();
+
unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
// Hereafter we start initializing things for "newUserId".
@@ -1763,9 +1772,10 @@
// Check if selected IME of current user supports handwriting.
if (userId == mCurrentUserId) {
- return mBindingController.supportsStylusHandwriting()
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ return userData.mBindingController.supportsStylusHandwriting()
&& (!connectionless
- || mBindingController.supportsConnectionlessStylusHandwriting());
+ || userData.mBindingController.supportsConnectionlessStylusHandwriting());
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
@@ -2095,7 +2105,8 @@
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
- if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
mHwController.setInkWindowInitializer(new InkWindowInitializer());
}
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
@@ -2216,6 +2227,8 @@
if (connectionIsActive != connectionWasActive) {
mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
}
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
@@ -2224,7 +2237,7 @@
Slog.d(TAG, "Avoiding IME startup and unbinding current input method.");
}
invalidateAutofillSessionLocked();
- mBindingController.unbindCurrentMethod();
+ userData.mBindingController.unbindCurrentMethod();
return InputBindResult.NO_EDITOR;
}
@@ -2256,9 +2269,8 @@
}
}
- mBindingController.unbindCurrentMethod();
-
- return mBindingController.bindCurrentMethod();
+ userData.mBindingController.unbindCurrentMethod();
+ return userData.mBindingController.bindCurrentMethod();
}
/**
@@ -2518,11 +2530,13 @@
@GuardedBy("ImfLock.class")
void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- mBindingController.setSelectedMethodId(null);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setSelectedMethodId(null);
+
// Callback before clean-up binding states.
// TODO(b/338461930): Check if this is still necessary or not.
onUnbindCurrentMethodByReset();
- mBindingController.unbindCurrentMethod();
+ userData.mBindingController.unbindCurrentMethod();
unbindCurrentClientLocked(unbindClientReason);
}
@@ -3099,7 +3113,8 @@
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
- mBindingController.setSelectedMethodId(id);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setSelectedMethodId(id);
if (mActivityManagerInternal.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3154,7 +3169,8 @@
@Nullable String delegatorPackageName,
@NonNull IConnectionlessHandwritingCallback callback) {
synchronized (ImfLock.class) {
- if (!mBindingController.supportsConnectionlessStylusHandwriting()) {
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ if (!userData.mBindingController.supportsConnectionlessStylusHandwriting()) {
Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
try {
callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
@@ -3237,7 +3253,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- if (!mBindingController.supportsStylusHandwriting()) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ if (!userData.mBindingController.supportsStylusHandwriting()) {
Slog.w(TAG,
"Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
return false;
@@ -3420,7 +3437,8 @@
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
- mBindingController.setCurrentMethodVisible();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
final boolean readyToDispatchToIme;
@@ -3528,7 +3546,8 @@
} else {
ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
}
- mBindingController.setCurrentMethodNotVisible();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setCurrentMethodNotVisible();
mVisibilityStateComputer.clearImeShowFlags();
// Cancel existing statsToken for show IME as we got a hide request.
ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
@@ -3810,7 +3829,8 @@
// Note that we can trust client's display ID as long as it matches
// to the display ID obtained from the window.
if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
- mBindingController.unbindCurrentMethod();
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ userData.mBindingController.unbindCurrentMethod();
}
}
}
@@ -4271,8 +4291,9 @@
mStylusIds.add(deviceId);
// a new Stylus is detected. If IME supports handwriting, and we don't have
// handwriting initialized, lets do it now.
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
if (!mHwController.getCurrentRequestId().isPresent()
- && mBindingController.supportsStylusHandwriting()) {
+ && userData.mBindingController.supportsStylusHandwriting()) {
scheduleResetStylusHandwriting();
}
}
@@ -4841,7 +4862,8 @@
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
- if (mBindingController.supportsStylusHandwriting()
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ if (userData.mBindingController.supportsStylusHandwriting()
&& getCurMethodLocked() != null && hasSupportedStylusLocked()) {
Slog.d(TAG, "Initializing Handwriting Spy");
mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
@@ -4866,11 +4888,12 @@
if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
return true;
}
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
- mBindingController.getCurMethodUid(),
+ userData.mBindingController.getCurMethodUid(),
mImeBindingState.mFocusedWindow);
if (session == null) {
Slog.e(TAG,
@@ -5164,7 +5187,8 @@
@GuardedBy("ImfLock.class")
void sendOnNavButtonFlagsChangedLocked() {
- final IInputMethodInvoker curMethod = mBindingController.getCurMethod();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final IInputMethodInvoker curMethod = userData.mBindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
@@ -5917,9 +5941,10 @@
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
mImeBindingState.dump(" ", p);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
- + mBindingController.isVisibleBound());
+ + userData.mBindingController.isVisibleBound());
p.println(" mCurToken=" + getCurTokenLocked());
p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId);
p.println(" mCurHostInputToken=" + mCurHostInputToken);
@@ -6413,7 +6438,8 @@
if (userId == mCurrentUserId) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
- mBindingController.unbindCurrentMethod();
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ userData.mBindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
var toDisable = settings.getEnabledInputMethodList();
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 7f00229..825cfcb 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -15,6 +15,7 @@
*/
package com.android.server.inputmethod;
+
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
@@ -25,18 +26,21 @@
import com.android.server.pm.UserManagerInternal;
import java.util.function.Consumer;
+import java.util.function.IntFunction;
final class UserDataRepository {
@GuardedBy("ImfLock.class")
private final SparseArray<UserData> mUserData = new SparseArray<>();
+ private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
+
@GuardedBy("ImfLock.class")
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
UserData userData = mUserData.get(userId);
if (userData == null) {
- userData = new UserData(userId);
+ userData = new UserData(userId, mBindingControllerFactory.apply(userId));
mUserData.put(userId, userData);
}
return userData;
@@ -49,7 +53,9 @@
}
}
- UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal) {
+ UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
+ @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
+ mBindingControllerFactory = bindingControllerFactory;
userManagerInternal.addUserLifecycleListener(
new UserManagerInternal.UserLifecycleListener() {
@Override
@@ -79,11 +85,16 @@
@UserIdInt
final int mUserId;
- /**
+ @NonNull
+ final InputMethodBindingController mBindingController;
+
+ /**
* Intended to be instantiated only from this file.
*/
- private UserData(@UserIdInt int userId) {
+ private UserData(@UserIdInt int userId,
+ @NonNull InputMethodBindingController bindingController) {
mUserId = userId;
+ mBindingController = bindingController;
}
}
}
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index babb6c2..13cc99c 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -108,16 +108,25 @@
return (flags & mask) != 0;
}
- public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ /**
+ * Called when a notification is newly posted. Checks whether that notification, and all other
+ * active notifications should be grouped or ungrouped atuomatically, and returns whether.
+ * @param sbn The posted notification.
+ * @param autogroupSummaryExists Whether a summary for this notification already exists.
+ * @return Whether the provided notification should be autogrouped synchronously.
+ */
+ public boolean onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ boolean sbnToBeAutogrouped = false;
try {
if (!sbn.isAppGroup()) {
- maybeGroup(sbn, autogroupSummaryExists);
+ sbnToBeAutogrouped = maybeGroup(sbn, autogroupSummaryExists);
} else {
maybeUngroup(sbn, false, sbn.getUserId());
}
} catch (Exception e) {
Slog.e(TAG, "Failure processing new notification", e);
}
+ return sbnToBeAutogrouped;
}
public void onNotificationRemoved(StatusBarNotification sbn) {
@@ -137,20 +146,22 @@
*
* And stores the list of upgrouped notifications & their flags
*/
- private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ private boolean maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
int flags = 0;
List<String> notificationsToGroup = new ArrayList<>();
List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ // Indicates whether the provided sbn should be autogrouped by the caller.
+ boolean sbnToBeAutogrouped = false;
synchronized (mUngroupedNotifications) {
- String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+ String packageKey = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
final ArrayMap<String, NotificationAttributes> children =
- mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+ mUngroupedNotifications.getOrDefault(packageKey, new ArrayMap<>());
NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags,
sbn.getNotification().getSmallIcon(), sbn.getNotification().color,
sbn.getNotification().visibility);
children.put(sbn.getKey(), attr);
- mUngroupedNotifications.put(key, children);
+ mUngroupedNotifications.put(packageKey, children);
if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
flags = getAutogroupSummaryFlags(children);
@@ -187,10 +198,20 @@
mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(),
attr);
}
- for (String key : notificationsToGroup) {
- mCallback.addAutoGroup(key);
+ for (String keyToGroup : notificationsToGroup) {
+ if (android.app.Flags.checkAutogroupBeforePost()) {
+ if (keyToGroup.equals(sbn.getKey())) {
+ // Autogrouping for the provided notification is to be done synchronously.
+ sbnToBeAutogrouped = true;
+ } else {
+ mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true);
+ }
+ } else {
+ mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true);
+ }
}
}
+ return sbnToBeAutogrouped;
}
/**
@@ -406,7 +427,7 @@
}
protected interface Callback {
- void addAutoGroup(String key);
+ void addAutoGroup(String key, boolean requestSort);
void removeAutoGroup(String key);
void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ca6ae63..3dd2f1e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2793,10 +2793,10 @@
return new GroupHelper(getContext(), getContext().getPackageManager(),
mAutoGroupAtCount, new GroupHelper.Callback() {
@Override
- public void addAutoGroup(String key) {
- synchronized (mNotificationLock) {
- addAutogroupKeyLocked(key);
- }
+ public void addAutoGroup(String key, boolean requestSort) {
+ synchronized (mNotificationLock) {
+ addAutogroupKeyLocked(key, requestSort);
+ }
}
@Override
@@ -6538,7 +6538,7 @@
}
@GuardedBy("mNotificationLock")
- void addAutogroupKeyLocked(String key) {
+ void addAutogroupKeyLocked(String key, boolean requestSort) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
return;
@@ -6546,7 +6546,9 @@
if (r.getSbn().getOverrideGroupKey() == null) {
addAutoGroupAdjustment(r, GroupHelper.AUTOGROUP_KEY);
EventLogTags.writeNotificationAutogrouped(key);
- mRankingHandler.requestSort();
+ if (!android.app.Flags.checkAutogroupBeforePost() || requestSort) {
+ mRankingHandler.requestSort();
+ }
}
}
@@ -8609,6 +8611,29 @@
notification.flags |= FLAG_NO_CLEAR;
}
+ // Posts the notification if it has a small icon, and potentially autogroup
+ // the new notification.
+ if (android.app.Flags.checkAutogroupBeforePost()) {
+ if (notification.getSmallIcon() != null && !isCritical(r)) {
+ StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
+ if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+ || oldSbn.getNotification().flags
+ != n.getNotification().flags) {
+ synchronized (mNotificationLock) {
+ boolean willBeAutogrouped = mGroupHelper.onNotificationPosted(n,
+ hasAutoGroupSummaryLocked(n));
+ if (willBeAutogrouped) {
+ // The newly posted notification will be autogrouped, but
+ // was not autogrouped onPost, to avoid an unnecessary sort.
+ // We add the autogroup key to the notification without a
+ // sort here, and it'll be sorted below with extractSignals.
+ addAutogroupKeyLocked(key, /*requestSort=*/false);
+ }
+ }
+ }
+ }
+ }
+
mRankingHelper.extractSignals(r);
mRankingHelper.sort(mNotificationList);
final int position = mRankingHelper.indexOf(mNotificationList, r);
@@ -8629,17 +8654,20 @@
notifyListenersPostedAndLogLocked(r, old, mTracker, maybeReport);
posted = true;
- StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
- if (oldSbn == null
- || !Objects.equals(oldSbn.getGroup(), n.getGroup())
- || oldSbn.getNotification().flags != n.getNotification().flags) {
- if (!isCritical(r)) {
- mHandler.post(() -> {
- synchronized (mNotificationLock) {
- mGroupHelper.onNotificationPosted(
- n, hasAutoGroupSummaryLocked(n));
- }
- });
+ if (!android.app.Flags.checkAutogroupBeforePost()) {
+ StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
+ if (oldSbn == null
+ || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+ || oldSbn.getNotification().flags
+ != n.getNotification().flags) {
+ if (!isCritical(r)) {
+ mHandler.post(() -> {
+ synchronized (mNotificationLock) {
+ mGroupHelper.onNotificationPosted(
+ n, hasAutoGroupSummaryLocked(n));
+ }
+ });
+ }
}
}
} else {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 6c93fe7..56e4590 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -89,6 +89,7 @@
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.pm.KnownPackages;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.pkg.PackageState;
@@ -289,6 +290,9 @@
getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL,
userFilter, null, null);
+ UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ umi.addUserLifecycleListener(new UserLifecycleListener());
+
restoreSettings();
// Wipe all shell overlays on boot, to recover from a potentially broken device
@@ -339,6 +343,7 @@
if (newUserId == mPrevStartedUserId) {
return;
}
+ Slog.i(TAG, "Updating overlays for starting user " + newUserId);
try {
traceBegin(TRACE_TAG_RRO, "OMS#onStartUser " + newUserId);
// ensure overlays in the settings are up-to-date, and propagate
@@ -515,14 +520,46 @@
}
}
+ /**
+ * Indicates that the given user is of great importance so that when it is created, we quickly
+ * update its overlays by using a Listener mechanism rather than a Broadcast mechanism. This
+ * is especially important for {@link UserManager#isHeadlessSystemUserMode() HSUM}'s MainUser,
+ * which is created and switched-to immediately on first boot.
+ */
+ private static boolean isHighPriorityUserCreation(UserInfo user) {
+ // TODO: Consider extending this to all created users (guarded behind a flag in that case).
+ return user != null && user.isMain();
+ }
+
+ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
+ @Override
+ public void onUserCreated(UserInfo user, Object token) {
+ if (isHighPriorityUserCreation(user)) {
+ final int userId = user.id;
+ try {
+ Slog.i(TAG, "Updating overlays for onUserCreated " + userId);
+ traceBegin(TRACE_TAG_RRO, "OMS#onUserCreated " + userId);
+ synchronized (mLock) {
+ updatePackageManagerLocked(mImpl.updateOverlaysForUser(userId));
+ }
+ } finally {
+ traceEnd(TRACE_TAG_RRO);
+ }
+ }
+ }
+ }
+
private final class UserReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
switch (intent.getAction()) {
case ACTION_USER_ADDED:
- if (userId != UserHandle.USER_NULL) {
+ UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userId != UserHandle.USER_NULL && !isHighPriorityUserCreation(userInfo)) {
try {
+ Slog.i(TAG, "Updating overlays for added user " + userId);
traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED");
synchronized (mLock) {
updatePackageManagerLocked(mImpl.updateOverlaysForUser(userId));
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index b19bc7d..dd3d512 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -313,10 +313,14 @@
adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2;
}
} else {
- // TODO (b/281648899) the third case is not always correct, fix that.
+ // Note: the third case when MODE == BALANCE, -W + sqrt(W * H * R), is the width to add
+ // so that, when removing the appropriate height, we get a bitmap of aspect ratio R and
+ // total surface of W * H. In other words it is the width to add to get the desired
+ // aspect ratio R, while preserving the total number of pixels W * H.
int widthToAdd = mode == REMOVE ? 0
: mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
- : (int) (0.5 + crop.height() - crop.width());
+ : (int) (0.5 - crop.width()
+ + Math.sqrt(crop.width() * crop.height() * screenRatio));
int availableWidth = bitmapSize.x - crop.width();
if (availableWidth >= widthToAdd) {
int widthToAddLeft = widthToAdd / 2;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 6ec557a..b3208bf 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -88,6 +88,7 @@
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.ActivityOptions.SourceInfo;
+import android.app.ApplicationStartInfo;
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.WaitResult;
import android.app.WindowConfiguration.WindowingMode;
@@ -845,6 +846,16 @@
&& !r.mTransitionController.isCollecting(r))) {
done(false /* abort */, info, "notifyWindowsDrawn", timestampNs);
}
+
+ if (android.app.Flags.appStartInfoTimestamps()) {
+ // Log here to match StatsD for time to first frame.
+ mLoggerHandler.post(
+ () -> mSupervisor.mService.mWindowManager.mAmInternal.addStartInfoTimestamp(
+ ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME,
+ timestampNs, r.getUid(), r.getPid(),
+ info.mLastLaunchedActivity.mUserId));
+ }
+
return infoSnapshot;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f3e1dfb..5e95a4b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4381,7 +4381,8 @@
*/
protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
- boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) {
+ boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId,
+ long timeout) {
ArrayList<ActivityRecord> activities;
synchronized (mGlobalLock) {
@@ -4426,7 +4427,7 @@
}
}
}
- dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll);
+ dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll, timeout);
}
if (!printedAnything) {
// Typically happpens when no task matches displayIdFilter
@@ -4440,7 +4441,7 @@
* there is a thread associated with the activity.
*/
private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw,
- ActivityRecord r, String[] args, boolean dumpAll) {
+ ActivityRecord r, String[] args, boolean dumpAll, long timeout) {
String innerPrefix = prefix + " ";
IApplicationThread appThread = null;
synchronized (mGlobalLock) {
@@ -4471,7 +4472,7 @@
pw.flush();
try (TransferPipe tp = new TransferPipe()) {
appThread.dumpActivity(tp.getWriteFd(), r.token, innerPrefix, args);
- tp.go(fd);
+ tp.go(fd, timeout);
} catch (IOException e) {
pw.println(innerPrefix + "Failure while dumping the activity: " + e);
} catch (RemoteException e) {
@@ -6970,7 +6971,8 @@
boolean dumpFocusedRootTaskOnly, int displayIdFilter,
@UserIdInt int userId) {
return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll,
- dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId);
+ dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId,
+ /* timeout= */ 5000);
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f37e88..4147249 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2738,6 +2738,10 @@
if (!mVisibleBackgroundUserEnabled) {
return true;
}
+ if (isPrivate()) {
+ // UserManager doesn't track the user visibility for private displays.
+ return true;
+ }
final int userId = UserHandle.getUserId(uid);
return userId == UserHandle.USER_SYSTEM
|| mWmService.mUmInternal.isUserVisible(userId, mDisplayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index eeedec3..19053f7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -306,6 +306,18 @@
}
/**
+ * An interface to be notified on window removal.
+ */
+ public interface OnWindowRemovedListener {
+ /**
+ * Called when a window is removed.
+ *
+ * @param token the client token
+ */
+ void onWindowRemoved(IBinder token);
+ }
+
+ /**
* An interface to be notified when keyguard exit animation should start.
*/
public interface KeyguardExitAnimationStartListener {
@@ -1076,6 +1088,20 @@
public abstract void clearBlockedApps();
/**
+ * Register a listener to receive a callback on window removal.
+ *
+ * @param listener the listener to be registered.
+ */
+ public abstract void registerOnWindowRemovedListener(OnWindowRemovedListener listener);
+
+ /**
+ * Removes the listener.
+ *
+ * @param listener the listener to be removed.
+ */
+ public abstract void unregisterOnWindowRemovedListener(OnWindowRemovedListener listener);
+
+ /**
* Moves the current focus to the adjacent activity if it has the latest created window.
*/
public abstract boolean moveFocusToAdjacentEmbeddedActivityIfNeeded();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index feede01..d0a50d5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -53,6 +53,7 @@
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -147,6 +148,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
@@ -489,6 +491,9 @@
private final RemoteCallbackList<IKeyguardLockedStateListener> mKeyguardLockedStateListeners =
new RemoteCallbackList<>();
+
+ private final List<OnWindowRemovedListener> mOnWindowRemovedListeners = new ArrayList<>();
+
private boolean mDispatchedKeyguardLockedState = false;
// VR Vr2d Display Id.
@@ -534,6 +539,21 @@
}
@Override
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (asProto) {
+ return;
+ }
+ mAtmService.dumpActivity(fd, pw, /* name= */ "all", /* args= */ new String[]{},
+ /* opti= */ 0,
+ /* dumpAll= */ true,
+ /* dumpVisibleRootTasksOnly= */ true,
+ /* dumpFocusedRootTaskOnly= */ false, INVALID_DISPLAY, UserHandle.USER_ALL,
+ /* timeout= */ 1000
+ );
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
doDump(fd, pw, args, asProto);
}
@@ -2073,7 +2093,11 @@
*/
void postWindowRemoveCleanupLocked(WindowState win) {
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "postWindowRemoveCleanupLocked: %s", win);
- mWindowMap.remove(win.mClient.asBinder());
+ final IBinder client = win.mClient.asBinder();
+ mWindowMap.remove(client);
+ if (sensitiveContentAppProtection()) {
+ notifyWindowRemovedListeners(client);
+ }
final DisplayContent dc = win.getDisplayContent();
dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */);
@@ -5335,6 +5359,23 @@
}
}
+ private void notifyWindowRemovedListeners(IBinder client) {
+ OnWindowRemovedListener[] windowRemovedListeners;
+ synchronized (mGlobalLock) {
+ if (mOnWindowRemovedListeners.isEmpty()) {
+ return;
+ }
+ windowRemovedListeners = new OnWindowRemovedListener[mOnWindowRemovedListeners.size()];
+ mOnWindowRemovedListeners.toArray(windowRemovedListeners);
+ }
+ mH.post(() -> {
+ int size = windowRemovedListeners.length;
+ for (int i = 0; i < size; i++) {
+ windowRemovedListeners[i].onWindowRemoved(client);
+ }
+ });
+ }
+
private void notifyWindowsChanged() {
WindowChangeListener[] windowChangeListeners;
synchronized (mGlobalLock) {
@@ -8868,6 +8909,20 @@
}
@Override
+ public void registerOnWindowRemovedListener(OnWindowRemovedListener listener) {
+ synchronized (mGlobalLock) {
+ mOnWindowRemovedListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void unregisterOnWindowRemovedListener(OnWindowRemovedListener listener) {
+ synchronized (mGlobalLock) {
+ mOnWindowRemovedListeners.remove(listener);
+ }
+ }
+
+ @Override
public boolean moveFocusToAdjacentEmbeddedActivityIfNeeded() {
synchronized (mGlobalLock) {
final WindowState focusedWindow = getFocusedWindow();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 85ab562..f553a5a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -455,10 +455,10 @@
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
-import android.security.KeyStore;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.AttestationUtils;
import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.telecom.TelecomManager;
@@ -6248,7 +6248,7 @@
try (KeyChainConnection keyChainConnection =
KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
IKeyChainService keyChain = keyChainConnection.getService();
- if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) {
+ if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyProperties.UID_SELF)) {
logInstallKeyPairFailure(caller, isCredentialManagementApp);
return false;
}
@@ -6583,7 +6583,7 @@
}
// As the caller will be granted access to the key, ensure no UID was specified, as
// it will not have the desired effect.
- if (keySpec.getUid() != KeyStore.UID_SELF) {
+ if (keySpec.getUid() != KeyProperties.UID_SELF) {
Slogf.e(LOG_TAG, "Only the caller can be granted access to the generated keypair.");
logGenerateKeyPairFailure(caller, isCredentialManagementApp);
return false;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8caf5ae..8755a80 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1618,7 +1618,8 @@
wm = WindowManagerService.main(context, inputManager, !mFirstBoot,
new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
- DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
+ DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
+ | DUMP_FLAG_PROTO);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
t.traceEnd();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index 1f0a375..70903cb 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -77,9 +77,13 @@
mCountDownLatch = new CountDownLatch(1);
// Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling
// from system.
- mBindingController =
- new InputMethodBindingController(
- mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch);
+ synchronized (ImfLock.class) {
+ mBindingController =
+ new InputMethodBindingController(
+ mInputMethodManagerService.getCurrentImeUserIdLocked(),
+ mInputMethodManagerService, mImeConnectionBindFlags,
+ mCountDownLatch);
+ }
}
@Test
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index b4cf799..cff2265 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -222,7 +222,7 @@
Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
false);
mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
- mMockInputMethodBindingController);
+ unusedUserId -> mMockInputMethodBindingController);
spyOn(mInputMethodManagerService);
// Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index a15b170..c3a87da 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -38,6 +39,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.IntFunction;
// This test is designed to run on both device and host (Ravenwood) side.
public final class UserDataRepositoryTest {
@@ -51,19 +53,34 @@
@Mock
private UserManagerInternal mMockUserManagerInternal;
+ @Mock
+ private InputMethodManagerService mMockInputMethodManagerService;
+
private Handler mHandler;
+ private IntFunction<InputMethodBindingController> mBindingControllerFactory;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mHandler = new Handler(Looper.getMainLooper());
+ mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
+
+ @Override
+ public InputMethodBindingController apply(int userId) {
+ return new InputMethodBindingController(userId, mMockInputMethodManagerService);
+ }
+ };
}
@Test
public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
// Create UserDataRepository and capture the user lifecycle listener
final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+ bindingControllerFactorySpy);
+
verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
final var listener = captor.getValue();
@@ -77,14 +94,20 @@
// Assert UserDataRepository contains the expected UserData
final var allUserData = collectUserData(repository);
assertThat(allUserData).hasSize(1);
- assertThat(allUserData.get(0).mUserId).isEqualTo(userInfo.id);
+ assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
+
+ // Assert UserDataRepository called the InputMethodBindingController creator function.
+ verify(bindingControllerFactorySpy).apply(ANY_USER_ID);
+ assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID);
}
@Test
public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
// Create UserDataRepository and capture the user lifecycle listener
final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+ userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
+
verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
final var listener = captor.getValue();
@@ -104,7 +127,8 @@
@Test
public void testGetOrCreate() {
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+ mBindingControllerFactory);
synchronized (ImfLock.class) {
final var userData = repository.getOrCreate(ANY_USER_ID);
@@ -114,6 +138,9 @@
final var allUserData = collectUserData(repository);
assertThat(allUserData).hasSize(1);
assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
+
+ // Assert UserDataRepository called the InputMethodBindingController creator function.
+ assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID);
}
private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
index 7aafa8e..5ddd8a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
@@ -26,7 +26,6 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
@@ -108,7 +107,7 @@
mMediaPorjectionCallback.onStart(exemptedRecorderPackage);
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -135,7 +134,7 @@
// when screen sharing is not active, no app window should be blocked.
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -158,8 +157,7 @@
mMediaPorjectionCallback.onStart(mMediaProjectionInfo);
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verify(mWindowManager, never())
- .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -168,7 +166,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo);
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
private void mockDisabledViaDeveloperOption() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index a20d935..8b65337 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -30,7 +30,6 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManagerInternal;
@@ -102,6 +101,8 @@
@Captor
ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor;
+ @Captor
+ private ArgumentCaptor<ArraySet<PackageInfo>> mPackageInfoCaptor;
@Mock
private MediaProjectionManager mProjectionManager;
@@ -309,7 +310,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -469,7 +470,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -480,7 +481,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -495,7 +496,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -519,7 +520,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -530,7 +531,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -541,7 +542,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -557,7 +558,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -574,7 +575,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -586,7 +587,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -598,7 +599,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -614,7 +615,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -640,7 +641,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -652,7 +653,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -666,7 +667,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(null);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -684,7 +685,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -702,7 +703,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -715,7 +716,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -727,7 +728,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -743,7 +744,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -773,7 +774,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification2, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -787,7 +788,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(null, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -801,7 +802,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, null);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -816,7 +817,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -829,7 +830,14 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ }
+
+ private void verifyNoBlockOrClearInteractionWithWindowManager() {
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ verify(mWindowManager, never()).clearBlockedApps();
+ verify(mWindowManager, never())
+ .removeBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
private void mockDisabledViaDevelopOption() {
diff --git a/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java
new file mode 100644
index 0000000..0bca59d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.autofill;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+@RunWith(JUnit4.class)
+public class SaveEventLoggerTest {
+
+ @Test
+ public void testTimestampsInitialized() {
+ SaveEventLogger mLogger = spy(SaveEventLogger.forSessionId(1, 1));
+
+ mLogger.maybeSetLatencySaveUiDisplayMillis();
+ mLogger.maybeSetLatencySaveRequestMillis();
+ mLogger.maybeSetLatencySaveFinishMillis();
+
+ ArgumentCaptor<Long> latencySaveUiDisplayMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveRequestMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveFinishMillis = ArgumentCaptor.forClass(Long.class);
+
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveUiDisplayMillis(latencySaveUiDisplayMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveRequestMillis(latencySaveRequestMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveFinishMillis(latencySaveFinishMillis.capture());
+
+ assertThat(latencySaveUiDisplayMillis.getValue())
+ .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveRequestMillis.getValue())
+ .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveFinishMillis.getValue())
+ .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ }
+
+ @Test
+ public void testTimestampsNotInitialized() {
+ SaveEventLogger mLogger =
+ spy(SaveEventLogger.forSessionId(1, SaveEventLogger.UNINITIATED_TIMESTAMP));
+
+ mLogger.maybeSetLatencySaveUiDisplayMillis();
+ mLogger.maybeSetLatencySaveRequestMillis();
+ mLogger.maybeSetLatencySaveFinishMillis();
+ ArgumentCaptor<Long> latencySaveUiDisplayMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveRequestMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveFinishMillis = ArgumentCaptor.forClass(Long.class);
+
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveUiDisplayMillis(latencySaveUiDisplayMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveRequestMillis(latencySaveRequestMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveFinishMillis(latencySaveFinishMillis.capture());
+
+ assertThat(latencySaveUiDisplayMillis.getValue())
+ .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveRequestMillis.getValue())
+ .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveFinishMillis.getValue())
+ .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 1194973..c7c97e4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -34,6 +34,7 @@
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -54,6 +55,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.StatusBarNotification;
@@ -271,7 +273,8 @@
}
@Test
- public void testAddSummary() {
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_alwaysAutogroup() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
mGroupHelper.onNotificationPosted(
@@ -279,13 +282,52 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ assertThat(mGroupHelper.onNotificationPosted(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false)).isFalse();
+ }
+ assertThat(mGroupHelper.onNotificationPosted(
+ getSbn(pkg, AUTOGROUP_AT_COUNT - 1, String.valueOf(AUTOGROUP_AT_COUNT - 1),
+ UserHandle.SYSTEM), false)).isTrue();
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_oneChildOngoing_summaryOngoing_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildOngoing_summaryOngoing() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -297,13 +339,33 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -315,13 +377,31 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -331,13 +411,34 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_summaryAutoCancelNoClear_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_NO_CLEAR;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_summaryAutoCancelNoClear() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -350,7 +451,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
@@ -617,7 +718,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
Mockito.reset(mCallback);
@@ -645,7 +746,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
Mockito.reset(mCallback);
@@ -664,7 +765,8 @@
}
@Test
- public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -674,7 +776,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
Mockito.reset(mCallback);
@@ -693,8 +795,8 @@
// < AUTOGROUP_AT_COUNT
final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM);
posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, true);
- verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
+ assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isFalse();
+ verify(mCallback, times(1)).addAutoGroup(sbn.getKey(), true);
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
@@ -703,7 +805,84 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
+ final String pkg = "package";
+ List<StatusBarNotification> posted = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ posted.add(sbn);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ Mockito.reset(mCallback);
+
+ for (int i = posted.size() - 2; i >= 0; i--) {
+ mGroupHelper.onNotificationRemoved(posted.remove(i));
+ }
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ Mockito.reset(mCallback);
+
+ // only one child remains
+ assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
+
+ // Add new notification; it should be autogrouped even though the total count is
+ // < AUTOGROUP_AT_COUNT
+ final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM);
+ posted.add(sbn);
+ assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue();
+ // addAutoGroup not called on sbn, because the autogrouping is expected to be done
+ // synchronously.
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAddSummary_sameIcon_sameColor_alwaysAutogroup() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+ final int iconColor = Color.BLUE;
+ final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
+ DEFAULT_VISIBILITY);
+
+ // Add notifications with same icon and color
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ // Check that the summary would have the same icon and color
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(attr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with the same color
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, true);
+
+ // Check that the summary was updated
+ //NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testAddSummary_sameIcon_sameColor() {
final String pkg = "package";
final Icon icon = mock(Icon.class);
@@ -721,7 +900,7 @@
// Check that the summary would have the same icon and color
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(attr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -761,7 +940,7 @@
// Check that the summary would have the same icon and color
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(initialAttr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -780,8 +959,9 @@
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
- public void testAddSummary_diffVisibility() {
+ public void testAddSummary_diffVisibility_alwaysAutogroup() {
final String pkg = "package";
final Icon icon = mock(Icon.class);
when(icon.sameAs(icon)).thenReturn(true);
@@ -798,7 +978,8 @@
// Check that the summary has private visibility
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(attr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -815,6 +996,48 @@
}
@Test
+ @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testAddSummary_diffVisibility() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+ final int iconColor = Color.BLUE;
+ final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
+ VISIBILITY_PRIVATE);
+
+ // Add notifications with same icon and color and default visibility (private)
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor);
+ assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isFalse();
+ }
+ // The last notification added will reach the autogroup threshold.
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT - 1,
+ String.valueOf(AUTOGROUP_AT_COUNT - 1), UserHandle.SYSTEM, null, icon, iconColor);
+ assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isTrue();
+
+ // Check that the summary has private visibility
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(attr));
+ // The last sbn is expected to be added to autogroup synchronously.
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with public visibility
+ sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+ sbn.getNotification().visibility = VISIBILITY_PUBLIC;
+ assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue();
+
+ // Check that the summary visibility was updated
+ NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
+ VISIBILITY_PUBLIC);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() {
final String pkg = "package";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3a0eba1..b366f92 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -311,6 +311,7 @@
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
+
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -331,8 +332,6 @@
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -348,6 +347,9 @@
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
@@ -5542,7 +5544,7 @@
public void testAddAutogroup_requestsSort() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.addAutogroupKeyLocked(r.getKey());
+ mService.addAutogroupKeyLocked(r.getKey(), true);
verify(mRankingHandler, times(1)).requestSort();
}
@@ -5562,12 +5564,30 @@
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
r.setOverrideGroupKey("TEST");
mService.addNotification(r);
- mService.addAutogroupKeyLocked(r.getKey());
+ mService.addAutogroupKeyLocked(r.getKey(), true);
verify(mRankingHandler, never()).requestSort();
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAutogroupSuppressSort_noSort() throws Exception {
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(r);
+ mService.addAutogroupKeyLocked(r.getKey(), false);
+
+ verify(mRankingHandler, never()).requestSort();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAutogroupOnPost_skipManualSort() throws Exception {
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(r);
+ verify(mRankingHandler, never()).requestSort();
+ }
+
+ @Test
public void testHandleRankingSort_sendsUpdateOnSignalExtractorChange() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
NotificationManagerService.WorkerHandler handler = mock(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 27d9d13..44d1b54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -114,6 +114,8 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -2813,6 +2815,41 @@
mDisplayContent.getKeepClearAreas());
}
+ @Test
+ public void testHasAccessConsidersUserVisibilityForBackgroundVisibleUsers() {
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+ final int appId = 1234;
+ final int userId1 = 11;
+ final int userId2 = 12;
+ final int uid1 = UserHandle.getUid(userId1, appId);
+ final int uid2 = UserHandle.getUid(userId2, appId);
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ int displayId = dc.getDisplayId();
+ doReturn(true).when(mWm.mUmInternal).isUserVisible(userId1, displayId);
+ doReturn(false).when(mWm.mUmInternal).isUserVisible(userId2, displayId);
+
+ assertTrue(dc.hasAccess(uid1));
+ assertFalse(dc.hasAccess(uid2));
+ }
+
+ @Test
+ public void testHasAccessIgnoresUserVisibilityForPrivateDisplay() {
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+ final int appId = 1234;
+ final int userId2 = 12;
+ final int uid2 = UserHandle.getUid(userId2, appId);
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.flags = FLAG_PRIVATE;
+ displayInfo.ownerUid = uid2;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ int displayId = dc.getDisplayId();
+
+ assertTrue(dc.hasAccess(uid2));
+
+ verify(mWm.mUmInternal, never()).isUserVisible(userId2, displayId);
+ }
+
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 9acda5f..e8bac66 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -62,6 +63,7 @@
import android.os.FileUtils;
import android.os.Handler;
import android.os.HwBinder;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -89,6 +91,7 @@
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.usb.flags.Flags;
import com.android.server.usb.hal.gadget.UsbGadgetHal;
import com.android.server.usb.hal.gadget.UsbGadgetHalInstance;
import com.android.server.utils.EventLogger;
@@ -586,6 +589,22 @@
*/
protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
+ protected static final String MTP_PACKAGE_NAME = "com.android.mtp";
+ protected static final String MTP_SERVICE_CLASS_NAME = "com.android.mtp.MtpService";
+
+ private boolean mIsMtpServiceBound = false;
+
+ /**
+ * {@link ServiceConnection} for {@link MtpService}.
+ */
+ private ServiceConnection mMtpServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {}
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {}
+ };
+
UsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager,
UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
super(looper);
@@ -915,6 +934,7 @@
private void updateUsbFunctions() {
updateMidiFunction();
+ updateMtpFunction();
}
private void updateMidiFunction() {
@@ -941,6 +961,67 @@
mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
}
+ /**
+ * Bind to MtpService when MTP or PTP is enabled. This is done to prevent activity manager
+ * from freezing the corresponding process.
+ */
+ private void updateMtpFunction() {
+ if (!Flags.enableBindToMtpService()) {
+ return;
+ }
+
+ boolean mtpEnabled = ((mCurrentFunctions & UsbManager.FUNCTION_MTP) != 0);
+ boolean ptpEnabled = ((mCurrentFunctions & UsbManager.FUNCTION_PTP) != 0);
+
+ if (DEBUG) {
+ Slog.d(TAG, "updateMtpFunction "
+ + ", mtpEnabled: " + mtpEnabled
+ + ", ptpEnabled: " + ptpEnabled
+ + ", mIsMtpServiceBound: " + mIsMtpServiceBound
+ );
+ }
+
+ if (mConfigured && (mtpEnabled || ptpEnabled)) {
+ bindToMtpService();
+ } else if (mIsMtpServiceBound) {
+ unbindMtpService();
+ }
+ }
+
+ private void bindToMtpService() {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(MTP_PACKAGE_NAME, MTP_SERVICE_CLASS_NAME));
+
+ if (DEBUG) Slog.d(TAG, "Binding to MtpService");
+
+ try {
+ mIsMtpServiceBound = mContext.bindServiceAsUser(
+ intent,
+ mMtpServiceConnection,
+ Context.BIND_AUTO_CREATE,
+ UserHandle.CURRENT
+ );
+ } catch (SecurityException exception) {
+ Slog.e(TAG, "Unable to bind to MtpService due to SecurityException", exception);
+ }
+
+ // Unbinding from the service if binding was not successful to release the connection.
+ // https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)
+ if (!mIsMtpServiceBound) {
+ unbindMtpService();
+ Slog.e(TAG, "Binding to MtpService failed");
+ }
+
+ if (DEBUG && mIsMtpServiceBound) Slog.d(TAG, "Successfully bound to MtpService");
+ }
+
+ private void unbindMtpService() {
+ if (DEBUG) Slog.d(TAG, "Unbinding from MtpService");
+
+ mContext.unbindService(mMtpServiceConnection);
+ mIsMtpServiceBound = false;
+ }
+
private void setScreenUnlockedFunctions(int operationId) {
setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
}
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
index a7c5ddb..cd96d76 100644
--- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -7,3 +7,10 @@
description: "This flag controls the restriction of usb overlay activities"
bug: "307231174"
}
+
+flag {
+ name: "enable_bind_to_mtp_service"
+ namespace: "usb"
+ description: "This flag enables binding to MtpService when in mtp/ptp modes"
+ bug: "332256525"
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 25e2d82..03ba8fa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13787,11 +13787,11 @@
* <p>This method returns valid data on devices with {@link
* android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled.
*
- * @deprecated Apps should use {@link getCarriersRestrictionRules} to retrieve the list of
+ * @deprecated Apps should use {@link #getCarrierRestrictionRules} to retrieve the list of
* allowed and excliuded carriers, as the result of this API is valid only when the excluded
* list is empty. This API could return an empty list, even if some restrictions are present.
*
- * @return List of {@link android.telephony.CarrierIdentifier}; empty list
+ * @return List of {@link android.service.carrier.CarrierIdentifier}; empty list
* means all carriers are allowed.
*
* @throws UnsupportedOperationException If the device does not have