Merge "Overload DeviceInfoUtils.getSecurityPatch method" into main
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 34f3b61..fd1a896 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -221,6 +221,17 @@
}
flag {
+ name: "cache_user_properties_correctly_read_only"
+ namespace: "multiuser"
+ description: "UserProperties cache needs to take into account who the callingUid is."
+ bug: "369198539"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+flag {
name: "cache_user_serial_number"
namespace: "multiuser"
description: "Optimise user serial number retrieval"
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index dbb6f92..5f62b8b 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -46,6 +46,7 @@
void userActivity(int displayId, long time, int event, int flags);
void wakeUp(long time, int reason, String details, String opPackageName);
+ void wakeUpWithDisplayId(long time, int reason, String details, String opPackageName, int displayId);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void goToSleep(long time, int reason, int flags);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index e4c12b6..b9bae5b 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1567,27 +1567,9 @@
}
/**
- * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
- * to turn on.
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on.
*
- * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
- * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
- * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
- * on then nothing will happen.
- *
- * <p>
- * This is what happens when the power key is pressed to turn on the screen.
- * </p><p>
- * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
- * </p>
- *
- * @param time The time when the request to wake up was issued, in the
- * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly
- * order the wake up request with other power management functions. It should be set
- * to the timestamp of the input event that caused the request to wake up.
- *
- * @see #userActivity
- * @see #goToSleep
+ * @see #wakeUp(long, int, String, int)
*
* @deprecated Use {@link #wakeUp(long, int, String)} instead.
* @removed Requires signature permission.
@@ -1598,30 +1580,9 @@
}
/**
- * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
- * to turn on.
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on.
*
- * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
- * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
- * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
- * on then nothing will happen.
- *
- * <p>
- * This is what happens when the power key is pressed to turn on the screen.
- * </p><p>
- * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
- * </p>
- *
- * @param time The time when the request to wake up was issued, in the
- * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly
- * order the wake up request with other power management functions. It should be set
- * to the timestamp of the input event that caused the request to wake up.
- *
- * @param details A free form string to explain the specific details behind the wake up for
- * debugging purposes.
- *
- * @see #userActivity
- * @see #goToSleep
+ * @see #wakeUp(long, int, String, int)
*
* @deprecated Use {@link #wakeUp(long, int, String)} instead.
* @hide
@@ -1635,9 +1596,23 @@
/**
* Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on.
*
- * <p>If the {@link android.view.Display#DEFAULT_DISPLAY default display} is turned off it will
- * be turned on. Additionally, if the device is asleep it will be awoken. If the {@link
- * android.view.Display#DEFAULT_DISPLAY default display} is already on then nothing will happen.
+ * @see #wakeUp(long, int, String, int)
+ * @hide
+ */
+ public void wakeUp(long time, @WakeReason int reason, String details) {
+ try {
+ mService.wakeUp(time, reason, details, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Forces the display with the supplied displayId to turn on.
+ *
+ * <p>If the corresponding display is turned off, it will be turned on. Additionally, if the
+ * device is asleep it will be awoken. If the corresponding display is already on then nothing
+ * will happen. If the corresponding display does not exist, then nothing will happen.
*
* <p>If the device is an Android TV playback device, it will attempt to turn on the
* HDMI-connected TV and become the current active source via the HDMI-CEC One Touch Play
@@ -1658,14 +1633,16 @@
*
* @param details A free form string to explain the specific details behind the wake up for
* debugging purposes.
+ * @param displayId The displayId of the display to be woken up.
*
* @see #userActivity
* @see #goToSleep
* @hide
*/
- public void wakeUp(long time, @WakeReason int reason, String details) {
+ public void wakeUp(long time, @WakeReason int reason, String details, int displayId) {
try {
- mService.wakeUp(time, reason, details, mContext.getOpPackageName());
+ mService.wakeUpWithDisplayId(time, reason, details, mContext.getOpPackageName(),
+ displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index e38281f..8ac5532 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -29,7 +29,6 @@
import static android.view.WindowInsets.Type.ime;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.window.flags.Flags.insetsControlSeq;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -877,9 +876,7 @@
@InsetsType int visibleTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (int i = 0, size = newState.sourceSize(); i < size; i++) {
- final InsetsSource source = insetsControlSeq()
- ? new InsetsSource(newState.sourceAt(i))
- : newState.sourceAt(i);
+ final InsetsSource source = new InsetsSource(newState.sourceAt(i));
@InsetsType int type = source.getType();
@AnimationType int animationType = getAnimationType(type);
final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId());
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 2e2ff1d..da788a7 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -29,7 +29,6 @@
import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.window.flags.Flags.insetsControlSeq;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -431,9 +430,6 @@
// Frame is changing while animating. Keep note of the new frame but keep existing frame
// until animation is finished.
- if (!insetsControlSeq()) {
- newSource = new InsetsSource(newSource);
- }
mPendingFrame = new Rect(newSource.getFrame());
mPendingVisibleFrame = newSource.getVisibleFrame() != null
? new Rect(newSource.getVisibleFrame())
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1cad81b..daa0c57 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -130,7 +130,6 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
-import static com.android.window.flags.Flags.insetsControlSeq;
import static com.android.window.flags.Flags.setScPropertiesInClient;
import static com.android.window.flags.Flags.systemUiImmersiveConfirmationDialog;
@@ -888,10 +887,7 @@
/** Non-{@code null} if {@link #mActivityConfigCallback} is not {@code null}. */
@Nullable
private ActivityWindowInfo mLastReportedActivityWindowInfo;
- @Nullable
- private final ClientWindowFrames mLastReportedFrames = insetsControlSeq()
- ? new ClientWindowFrames()
- : null;
+ private final ClientWindowFrames mLastReportedFrames = new ClientWindowFrames();
private int mLastReportedInsetsStateSeq = getInitSeq();
private int mLastReportedActiveControlsSeq = getInitSeq();
@@ -2316,9 +2312,6 @@
}
private void onClientWindowFramesChanged(@NonNull ClientWindowFrames inOutFrames) {
- if (mLastReportedFrames == null) {
- return;
- }
if (isIncomingSeqStale(mLastReportedFrames.seq, inOutFrames.seq)) {
// If the incoming is stale, use the last reported instead.
inOutFrames.setTo(mLastReportedFrames);
@@ -2329,14 +2322,12 @@
}
private void onInsetsStateChanged(@NonNull InsetsState insetsState) {
- if (insetsControlSeq()) {
- if (isIncomingSeqStale(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
- // The incoming is stale. Skip.
- return;
- }
- // Keep track of the latest.
- mLastReportedInsetsStateSeq = insetsState.getSeq();
+ if (isIncomingSeqStale(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
+ // The incoming is stale. Skip.
+ return;
}
+ // Keep track of the latest.
+ mLastReportedInsetsStateSeq = insetsState.getSeq();
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
@@ -2351,15 +2342,13 @@
return;
}
- if (insetsControlSeq()) {
- if (isIncomingSeqStale(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
- // The incoming is stale. Skip.
- activeControls.release();
- return;
- }
- // Keep track of the latest.
- mLastReportedActiveControlsSeq = activeControls.getSeq();
+ if (isIncomingSeqStale(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
+ // The incoming is stale. Skip.
+ activeControls.release();
+ return;
}
+ // Keep track of the latest.
+ mLastReportedActiveControlsSeq = activeControls.getSeq();
final InsetsSourceControl[] controls = activeControls.get();
if (mTranslator != null) {
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 11d4db3..f0ea7a8 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -68,17 +68,6 @@
flag {
namespace: "windowing_sdk"
- name: "insets_control_seq"
- description: "Add seqId to InsetsControls to ensure the stale update is ignored"
- bug: "339380439"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "windowing_sdk"
name: "move_animation_options_to_change"
description: "Move AnimationOptions from TransitionInfo to each Change"
bug: "327332488"
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index e240a08..6327211 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -71,7 +71,6 @@
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.SystemProperties;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -1599,7 +1598,6 @@
nativeCreateASurfaceControlFromSurface(mViewRootImpl.mSurface));
}
- @EnableFlags(Flags.FLAG_INSETS_CONTROL_SEQ)
@Test
public void testHandleInsetsControlChanged() {
mView = new View(sContext);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 9c7d644..8c102eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -22,6 +22,7 @@
import android.graphics.Color
import android.graphics.Point
import android.hardware.input.InputManager
+import android.os.Bundle
import android.os.Handler
import android.view.MotionEvent.ACTION_DOWN
import android.view.SurfaceControl
@@ -29,7 +30,12 @@
import android.view.View.OnClickListener
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageButton
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags
import com.android.wm.shell.R
@@ -67,6 +73,20 @@
captionView.setOnTouchListener(onCaptionTouchListener)
captionHandle.setOnTouchListener(onCaptionTouchListener)
captionHandle.setOnClickListener(onCaptionButtonClickListener)
+ captionHandle.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun sendAccessibilityEvent(host: View, eventType: Int) {
+ when (eventType) {
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+ AccessibilityEvent.TYPE_VIEW_HOVER_EXIT -> {
+ // Caption Handle itself can't get a11y focus because it's under the status
+ // bar, so pass through TYPE_VIEW_HOVER a11y events to the status bar
+ // input layer, so that it can get a11y focus on the caption handle's behalf
+ statusBarInputLayer?.view?.sendAccessibilityEvent(eventType)
+ }
+ else -> super.sendAccessibilityEvent(host, eventType)
+ }
+ }
+ }
}
override fun bindData(
@@ -134,9 +154,53 @@
captionHandle.dispatchTouchEvent(event)
return@setOnTouchListener true
}
+ setupAppHandleA11y(view)
windowManagerWrapper.updateViewLayout(view, lp)
}
+ private fun setupAppHandleA11y(view: View) {
+ view.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ // Allow the status bar input layer to be a11y clickable so it can interact with
+ // a11y services on behalf of caption handle (due to being under status bar)
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ info.addAction(AccessibilityAction.ACTION_CLICK)
+ host.isClickable = true
+ }
+
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ // Passthrough the a11y click action so the caption handle, so that app handle menu
+ // is opened on a11y click, similar to a real click
+ if (action == AccessibilityAction.ACTION_CLICK.id) {
+ captionHandle.performClick()
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+
+ override fun onPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) {
+ super.onPopulateAccessibilityEvent(host, event)
+ // When the status bar input layer is focused, use the content description of the
+ // caption handle so that it appears as "App handle" and not "Unlabelled view"
+ if (event.eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+ event.text.add(captionHandle.contentDescription)
+ }
+ }
+ }
+
+ // Update a11y action text so that Talkback announces "Press double tap to open app handle
+ // menu" while focused on status bar input layer
+ ViewCompat.replaceAccessibilityAction(
+ view, AccessibilityActionCompat.ACTION_CLICK, "Open app handle menu", null
+ )
+ }
+
private fun updateStatusBarInputLayer(globalPosition: Point) {
statusBarInputLayer?.setPosition(
SurfaceControl.Transaction(),
@@ -173,7 +237,8 @@
return taskInfo.taskDescription
?.let { taskDescription ->
if (Color.alpha(taskDescription.statusBarColor) != 0 &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ ) {
Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
} else {
taskDescription.systemBarsAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
deleted file mode 100644
index 1051549..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.page
-
-import android.os.Bundle
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.AccessAlarm
-import androidx.compose.material.icons.outlined.MusicNote
-import androidx.compose.material.icons.outlined.MusicOff
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.preference.SliderPreference
-import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-
-private const val TITLE = "Sample Slider"
-
-object SliderPageProvider : SettingsPageProvider {
- override val name = "Slider"
- private val owner = createSettingsPage()
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create("Simple Slider", owner)
- .setUiLayoutFn {
- SliderPreference(object : SliderPreferenceModel {
- override val title = "Simple Slider"
- override val initValue = 40
- })
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create("Slider with icon", owner)
- .setUiLayoutFn {
- SliderPreference(object : SliderPreferenceModel {
- override val title = "Slider with icon"
- override val initValue = 30
- override val onValueChangeFinished = {
- println("onValueChangeFinished")
- }
- override val icon = Icons.Outlined.AccessAlarm
- })
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create("Slider with changeable icon", owner)
- .setUiLayoutFn {
- val initValue = 0
- var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
- var sliderPosition by remember { mutableStateOf(initValue) }
- SliderPreference(object : SliderPreferenceModel {
- override val title = "Slider with changeable icon"
- override val initValue = initValue
- override val onValueChange = { it: Int ->
- sliderPosition = it
- icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
- }
- override val onValueChangeFinished = {
- println("onValueChangeFinished: the value is $sliderPosition")
- }
- override val icon = icon
- })
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create("Slider with steps", owner)
- .setUiLayoutFn {
- SliderPreference(object : SliderPreferenceModel {
- override val title = "Slider with steps"
- override val initValue = 2
- override val valueRange = 1..5
- override val showSteps = true
- })
- }.build()
- )
-
- return entryList
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
- }
-
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
- }
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun SliderPagePreview() {
- SettingsTheme {
- SliderPageProvider.Page(null)
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
new file mode 100644
index 0000000..89b10ee
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SliderPreference
+import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
+
+private const val TITLE = "Sample Slider"
+
+object SliderPageProvider : SettingsPageProvider {
+ override val name = "Slider"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("Simple Slider", owner)
+ .setUiLayoutFn {
+ SliderPreference(
+ object : SliderPreferenceModel {
+ override val title = "Simple Slider"
+ override val initValue = 40
+ }
+ )
+ }
+ .build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Slider with icon", owner)
+ .setUiLayoutFn {
+ SliderPreference(
+ object : SliderPreferenceModel {
+ override val title = "Slider with icon"
+ override val initValue = 30
+ override val onValueChangeFinished = {
+ println("onValueChangeFinished")
+ }
+ override val iconStart = Icons.Outlined.AccessAlarm
+ }
+ )
+ }
+ .build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Slider with changeable icon", owner)
+ .setUiLayoutFn {
+ val initValue = 0
+ var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
+ var sliderPosition by remember { mutableStateOf(initValue) }
+ SliderPreference(
+ object : SliderPreferenceModel {
+ override val title = "Slider with changeable icon"
+ override val initValue = initValue
+ override val onValueChange = { it: Int ->
+ sliderPosition = it
+ icon =
+ if (it > 0) Icons.Outlined.MusicNote
+ else Icons.Outlined.MusicOff
+ }
+ override val onValueChangeFinished = {
+ println("onValueChangeFinished: the value is $sliderPosition")
+ }
+ override val iconEnd = icon
+ }
+ )
+ }
+ .build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Slider with steps", owner)
+ .setUiLayoutFn {
+ SliderPreference(
+ object : SliderPreferenceModel {
+ override val title = "Slider with steps"
+ override val initValue = 2
+ override val valueRange = 1..5
+ override val showSteps = true
+ }
+ )
+ }
+ .build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SliderPagePreview() {
+ SettingsTheme { SliderPageProvider.Page(null) }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
index 3a96a70..ca36e5b 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
@@ -60,7 +60,7 @@
override val onValueChangeFinished = {
println("onValueChangeFinished")
}
- override val icon = Icons.Outlined.AccessAlarm
+ override val iconStart = Icons.Outlined.AccessAlarm
})
SliderPreference(object : SliderPreferenceModel {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 70d353d..7e1df16 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
@@ -25,11 +26,14 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.ButtonDefaults
@@ -51,7 +55,7 @@
import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.divider
-import androidx.compose.material.icons.automirrored.outlined.Launch
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
data class ActionButton(
val text: String,
@@ -62,48 +66,64 @@
@Composable
fun ActionButtons(actionButtons: List<ActionButton>) {
- Row(
- Modifier
- .padding(SettingsDimension.buttonPadding)
- .clip(SettingsShape.CornerExtraLarge)
- .height(IntrinsicSize.Min)
- ) {
- for ((index, actionButton) in actionButtons.withIndex()) {
- if (index > 0) ButtonDivider()
- ActionButton(actionButton)
+ if (isSpaExpressiveEnabled) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceAround,
+ modifier = Modifier
+ .padding(SettingsDimension.buttonPadding)
+ .height(IntrinsicSize.Min)
+ .fillMaxWidth()
+ ) {
+ for (actionButton in actionButtons) {
+ ActionButton(actionButton)
+ }
+ }
+ } else {
+ Row(
+ Modifier
+ .padding(SettingsDimension.buttonPadding)
+ .clip(SettingsShape.CornerExtraLarge)
+ .height(IntrinsicSize.Min)
+ ) {
+ for ((index, actionButton) in actionButtons.withIndex()) {
+ if (index > 0) ButtonDivider()
+ ActionButton(actionButton)
+ }
}
}
}
@Composable
private fun RowScope.ActionButton(actionButton: ActionButton) {
- FilledTonalButton(
- onClick = actionButton.onClick,
- modifier = Modifier
- .weight(1f)
- .fillMaxHeight(),
- enabled = actionButton.enabled,
- // Because buttons could appear, disappear or change positions, reset the interaction source
- // to prevent highlight the wrong button.
- interactionSource = remember(actionButton) { MutableInteractionSource() },
- shape = RectangleShape,
- colors = ButtonDefaults.filledTonalButtonColors(
- containerColor = MaterialTheme.colorScheme.surface,
- contentColor = MaterialTheme.colorScheme.primary,
- disabledContainerColor = MaterialTheme.colorScheme.surface,
- ),
- contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp),
- ) {
+ if (isSpaExpressiveEnabled) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Icon(
- imageVector = actionButton.imageVector,
- contentDescription = null,
- modifier = Modifier.size(SettingsDimension.itemIconSize),
- )
+ FilledTonalButton(
+ onClick = actionButton.onClick,
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ .clip(RoundedCornerShape(100.dp)),
+ enabled = actionButton.enabled,
+ // Because buttons could appear, disappear or change positions, reset the interaction source
+ // to prevent highlight the wrong button.
+ interactionSource = remember(actionButton) { MutableInteractionSource() },
+ shape = RectangleShape,
+ colors = ButtonDefaults.filledTonalButtonColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ disabledContainerColor = MaterialTheme.colorScheme.surface,
+ ),
+ contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp),
+ ) {
+ Icon(
+ imageVector = actionButton.imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ )
+ }
Box(
modifier = Modifier
- .padding(top = 4.dp)
- .fillMaxHeight(),
+ .padding(top = 6.dp),
contentAlignment = Alignment.Center,
) {
Text(
@@ -113,6 +133,44 @@
)
}
}
+ } else {
+ FilledTonalButton(
+ onClick = actionButton.onClick,
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxHeight(),
+ enabled = actionButton.enabled,
+ // Because buttons could appear, disappear or change positions, reset the interaction source
+ // to prevent highlight the wrong button.
+ interactionSource = remember(actionButton) { MutableInteractionSource() },
+ shape = RectangleShape,
+ colors = ButtonDefaults.filledTonalButtonColors(
+ containerColor = MaterialTheme.colorScheme.surface,
+ contentColor = MaterialTheme.colorScheme.primary,
+ disabledContainerColor = MaterialTheme.colorScheme.surface,
+ ),
+ contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp),
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Icon(
+ imageVector = actionButton.imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ )
+ Box(
+ modifier = Modifier
+ .padding(top = 4.dp)
+ .fillMaxHeight(),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = actionButton.text,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.labelMedium,
+ )
+ }
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
index 7bca38f..b2a3aac 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
@@ -16,6 +16,10 @@
package com.android.settingslib.spa.widget.preference
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccessAlarm
import androidx.compose.material.icons.outlined.MusicNote
@@ -27,30 +31,25 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
+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.util.EntryHighlight
import com.android.settingslib.spa.widget.ui.SettingsSlider
-/**
- * The widget model for [SliderPreference] widget.
- */
+/** The widget model for [SliderPreference] widget. */
interface SliderPreferenceModel {
- /**
- * The title of this [SliderPreference].
- */
+ /** The title of this [SliderPreference]. */
val title: String
- /**
- * The initial position of the [SliderPreference].
- */
+ /** The initial position of the [SliderPreference]. */
val initValue: Int
- /**
- * The value range for this [SliderPreference].
- */
+ /** The value range for this [SliderPreference]. */
val valueRange: IntRange
get() = 0..100
@@ -69,15 +68,23 @@
get() = null
/**
- * The icon image for [SliderPreference]. If not specified, the slider hides the icon by default.
+ * The start icon image for [SliderPreference]. If not specified, the slider hides the icon by
+ * default.
*/
- val icon: ImageVector?
+ val iconStart: ImageVector?
get() = null
/**
- * Indicates whether to show step marks. If show step marks, when user finish sliding,
- * the slider will automatically jump to the nearest step mark. Otherwise, the slider hides
- * the step marks by default.
+ * The end icon image for [SliderPreference]. If not specified, the slider hides the icon by
+ * default.
+ */
+ val iconEnd: ImageVector?
+ get() = null
+
+ /**
+ * Indicates whether to show step marks. If show step marks, when user finish sliding, the
+ * slider will automatically jump to the nearest step mark. Otherwise, the slider hides the step
+ * marks by default.
*
* The step is fixed to 1.
*/
@@ -99,7 +106,8 @@
valueRange = model.valueRange,
onValueChange = model.onValueChange,
onValueChangeFinished = model.onValueChangeFinished,
- icon = model.icon,
+ iconStart = model.iconStart,
+ iconEnd = model.iconEnd,
showSteps = model.showSteps,
)
}
@@ -113,27 +121,45 @@
valueRange: IntRange = 0..100,
onValueChange: ((value: Int) -> Unit)? = null,
onValueChangeFinished: (() -> Unit)? = null,
- icon: ImageVector? = null,
+ iconStart: ImageVector? = null,
+ iconEnd: ImageVector? = null,
showSteps: Boolean = false,
) {
+
BaseLayout(
title = title,
subTitle = {
- SettingsSlider(
- initValue,
- modifier,
- valueRange,
- onValueChange,
- onValueChangeFinished,
- showSteps
- )
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ if (iconStart != null) {
+ SliderIcon(icon = iconStart)
+ Spacer(Modifier.padding(SettingsDimension.paddingSmall))
+ }
+ Box(Modifier.weight(1f)) {
+ SettingsSlider(
+ initValue,
+ modifier,
+ valueRange,
+ onValueChange,
+ onValueChangeFinished,
+ showSteps,
+ )
+ }
+ if (iconEnd != null) {
+ Spacer(Modifier.padding(SettingsDimension.paddingSmall))
+ SliderIcon(icon = iconEnd)
+ }
+ }
},
- icon = if (icon != null) ({
- Icon(imageVector = icon, contentDescription = null)
- }) else null,
)
}
+@Composable
+fun SliderIcon(icon: ImageVector) {
+ Box(modifier = Modifier.padding(8.dp), contentAlignment = Alignment.Center) {
+ Icon(imageVector = icon, contentDescription = null)
+ }
+}
+
@Preview
@Composable
private fun SliderPreferencePreview() {
@@ -147,7 +173,7 @@
onValueChangeFinished = {
println("onValueChangeFinished: the value is $sliderPosition")
},
- icon = Icons.Outlined.AccessAlarm,
+ iconStart = Icons.Outlined.AccessAlarm,
)
}
}
@@ -163,7 +189,22 @@
onValueChange = { it: Int ->
icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
},
- icon = icon,
+ iconEnd = icon,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SliderPreferenceTwoIconPreview() {
+ SettingsTheme {
+ val iconStart by remember { mutableStateOf(Icons.Outlined.MusicNote) }
+ val iconEnd by remember { mutableStateOf(Icons.Outlined.MusicOff) }
+ SliderPreference(
+ title = "Media Volume",
+ initValue = 40,
+ iconStart = iconStart,
+ iconEnd = iconEnd,
)
}
}
@@ -172,11 +213,6 @@
@Composable
private fun SliderPreferenceStepsPreview() {
SettingsTheme {
- SliderPreference(
- title = "Display Text",
- initValue = 2,
- valueRange = 1..5,
- showSteps = true,
- )
+ SliderPreference(title = "Display Text", initValue = 2, valueRange = 1..5, showSteps = true)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 312e62d..94d3b2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -62,7 +62,9 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Rule;
@@ -96,6 +98,7 @@
private FalsingA11yDelegate mFalsingA11yDelegate;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
@@ -106,6 +109,7 @@
mSecurityViewFlipper = new KeyguardSecurityViewFlipper(getContext());
mSecurityViewFlipper.setId(View.generateViewId());
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
+ mKeyguardSecurityContainer.setBackgroundExecutor(mExecutor);
mKeyguardSecurityContainer.setRight(VIEW_WIDTH);
mKeyguardSecurityContainer.setLeft(0);
mKeyguardSecurityContainer.setTop(0);
@@ -342,7 +346,7 @@
@Test
public void testTwoOrMoreUsersDoesAllowDropDown() {
- // GIVEN one user has been setup
+ // GIVEN two users have been setup
ArrayList<UserRecord> records = buildUserRecords(2);
when(mUserSwitcherController.getCurrentUserRecord()).thenReturn(records.get(0));
when(mUserSwitcherController.getUsers()).thenReturn(records);
@@ -350,7 +354,7 @@
// WHEN UserSwitcherViewMode is initialized
setupUserSwitcher();
- // THEN the UserSwitcher anchor should not be clickable
+ // THEN the UserSwitcher anchor should be clickable
ViewGroup anchor = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_anchor);
assertThat(anchor.isClickable()).isTrue();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ffbc85c..f05cbf4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -109,6 +109,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/** Determines how the bouncer is displayed to the user. */
public class KeyguardSecurityContainer extends ConstraintLayout {
@@ -170,6 +172,7 @@
private ViewMode mViewMode = new DefaultViewMode();
private boolean mIsInteractable;
protected ViewMediatorCallback mViewMediatorCallback;
+ private Executor mBgExecutor;
/*
* Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
* yet been called on it. This will happen when the ViewController is initialized.
@@ -352,6 +355,10 @@
updateBiometricRetry(securityMode, faceAuthEnabled);
}
+ void setBackgroundExecutor(Executor bgExecutor) {
+ mBgExecutor = bgExecutor;
+ }
+
void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback,
@@ -367,7 +374,7 @@
mViewMode = new OneHandedViewMode();
break;
case MODE_USER_SWITCHER:
- mViewMode = new UserSwitcherViewMode(userSwitcherCallback);
+ mViewMode = new UserSwitcherViewMode(userSwitcherCallback, mBgExecutor);
break;
default:
mViewMode = new DefaultViewMode();
@@ -991,6 +998,7 @@
private FalsingManager mFalsingManager;
private UserSwitcherController mUserSwitcherController;
private KeyguardUserSwitcherPopupMenu mPopup;
+ private Executor mBgExecutor;
private Resources mResources;
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
this::setupUserSwitcher;
@@ -998,8 +1006,9 @@
private UserSwitcherCallback mUserSwitcherCallback;
private FalsingA11yDelegate mFalsingA11yDelegate;
- UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
+ UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback, Executor bgExecutor) {
mUserSwitcherCallback = userSwitcherCallback;
+ mBgExecutor = bgExecutor;
}
@Override
@@ -1068,18 +1077,22 @@
mView.removeView(mUserSwitcher);
}
- private Drawable findLargeUserIcon(int userId) {
- Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId);
- if (userIcon != null) {
- int iconSize =
- mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_icon_size);
- return CircleFramedDrawable.getInstance(
- mView.getContext(),
- Icon.scaleDownIfNecessary(userIcon, iconSize, iconSize)
- );
- }
-
- return UserIcons.getDefaultUserIcon(mResources, userId, false);
+ private void findLargeUserIcon(int userId, Consumer<Drawable> consumer) {
+ mBgExecutor.execute(() -> {
+ Drawable icon;
+ Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId);
+ if (userIcon != null) {
+ int iconSize = mResources.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_icon_size);
+ icon = CircleFramedDrawable.getInstance(
+ mView.getContext(),
+ Icon.scaleDownIfNecessary(userIcon, iconSize, iconSize)
+ );
+ } else {
+ icon = UserIcons.getDefaultUserIcon(mResources, userId, false);
+ }
+ consumer.accept(icon);
+ });
}
@Override
@@ -1136,8 +1149,15 @@
return;
}
final String currentUserName = mUserSwitcherController.getCurrentUserName();
- Drawable userIcon = findLargeUserIcon(currentUser.info.id);
- ((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon);
+ findLargeUserIcon(currentUser.info.id,
+ (Drawable userIcon) -> {
+ mView.post(() -> {
+ ImageView view = (ImageView) mView.findViewById(R.id.user_icon);
+ if (view != null) {
+ view.setImageDrawable(userIcon);
+ }
+ });
+ });
mUserSwitcher.setText(currentUserName);
KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 2d28a18..a408211 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -76,6 +76,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
@@ -102,6 +103,7 @@
import java.io.File;
import java.util.Arrays;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -426,6 +428,7 @@
private final Provider<JavaAdapter> mJavaAdapter;
private final DeviceProvisionedController mDeviceProvisionedController;
private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
+ private final Executor mBgExecutor;
@Nullable
private Job mSceneTransitionCollectionJob;
@@ -459,6 +462,7 @@
DevicePolicyManager devicePolicyManager,
KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
+ @Background Executor bgExecutor,
Provider<DeviceEntryInteractor> deviceEntryInteractor
) {
super(view);
@@ -493,11 +497,13 @@
mDeviceProvisionedController = deviceProvisionedController;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mDevicePolicyManager = devicePolicyManager;
+ mBgExecutor = bgExecutor;
}
@Override
public void onInit() {
mSecurityViewFlipperController.init();
+ mView.setBackgroundExecutor(mBgExecutor);
updateResources();
configureMode();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 93cd1cf4..5e05dab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -403,7 +403,7 @@
SubCategoryTitle(subCategory.label)
subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
if (index > 0) {
- HorizontalDivider()
+ HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh)
}
ShortcutView(Modifier.padding(vertical = 24.dp), searchQuery, shortcut)
}
@@ -482,7 +482,7 @@
Spacer(Modifier.height(8.dp))
subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
if (index > 0) {
- HorizontalDivider()
+ HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh)
}
ShortcutView(Modifier.padding(vertical = 16.dp), searchQuery, shortcut)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index e444db4..5fe5cb3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -76,6 +76,7 @@
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
@@ -84,6 +85,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -172,6 +174,7 @@
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var fakeSceneDataSource: FakeSceneDataSource
+ private val executor = FakeExecutor(FakeSystemClock())
private lateinit var underTest: KeyguardSecurityContainerController
@@ -210,13 +213,9 @@
featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
- mSetFlagsRule.enableFlags(
- AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES,
- )
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
if (!SceneContainerFlag.isEnabled) {
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
}
keyguardPasswordViewController =
@@ -239,7 +238,7 @@
mSelectedUserInteractor,
keyguardKeyboardInteractor,
null,
- mUserActivityNotifier
+ mUserActivityNotifier,
)
kosmos = testKosmos()
@@ -283,6 +282,7 @@
devicePolicyManager,
kosmos.keyguardDismissTransitionInteractor,
{ primaryBouncerInteractor },
+ executor,
) {
deviceEntryInteractor
}
@@ -298,7 +298,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
}
@@ -334,7 +334,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
// Update rotation. Should trigger update
@@ -347,7 +347,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
}
@@ -359,7 +359,7 @@
MotionEvent.ACTION_DOWN,
/* x= */ 0f,
/* y= */ 0f,
- /* metaState= */ 0
+ /* metaState= */ 0,
)
)
}
@@ -386,7 +386,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
}
@@ -401,7 +401,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
}
@@ -416,7 +416,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
}
@@ -431,7 +431,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
}
@@ -446,7 +446,7 @@
eq(falsingManager),
eq(userSwitcherController),
any(),
- eq(falsingA11yDelegate)
+ eq(falsingA11yDelegate),
)
}
@@ -462,7 +462,7 @@
.showMessage(
/* message= */ context.getString(R.string.keyguard_unlock_to_continue),
/* colorState= */ null,
- /* animated= */ true
+ /* animated= */ true,
)
}
@@ -496,7 +496,7 @@
/* authenticated= */ true,
TARGET_USER_ID,
/* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
+ SecurityMode.SimPin,
)
// THEN the next security method of None will dismiss keyguard.
@@ -514,7 +514,7 @@
/* authenticated= */ true,
TARGET_USER_ID,
/* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
+ SecurityMode.SimPin,
)
// THEN no action has happened, which will not dismiss the security screens
@@ -539,7 +539,7 @@
/* authenticated= */ true,
TARGET_USER_ID,
/* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
+ SecurityMode.SimPin,
)
// THEN the next security method of None will dismiss keyguard.
@@ -564,7 +564,7 @@
/* authenticated= */ true,
TARGET_USER_ID,
/* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
+ SecurityMode.SimPin,
)
// THEN the next security method of None will dismiss keyguard.
@@ -589,7 +589,7 @@
/* authenticated= */ true,
TARGET_USER_ID,
/* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
+ SecurityMode.SimPin,
)
// THEN we will not show the password screen.
@@ -615,7 +615,7 @@
/* authenticated= */ true,
TARGET_USER_ID,
/* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
+ SecurityMode.SimPin,
)
// THEN we will not show the password screen.
@@ -717,7 +717,7 @@
// Now simulate a config change
testableResources.addOverride(
R.integer.keyguard_host_view_gravity,
- Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM,
)
underTest.updateResources()
verify(view).layoutParams = any()
@@ -728,7 +728,7 @@
testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
testableResources.addOverride(
R.integer.keyguard_host_view_one_handed_gravity,
- Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM,
)
// Start disabled.
@@ -948,7 +948,7 @@
/* expiringUserId = */ mainUserId,
/* mainUserId = */ mainUserId,
/* remainingBeforeWipe = */ 1,
- /* failedAttempts = */ 1
+ /* failedAttempts = */ 1,
)
verify(view)
@@ -965,14 +965,14 @@
/* expiringUserId = */ secondaryUserId,
/* mainUserId = */ mainUserId,
/* remainingBeforeWipe = */ 1,
- /* failedAttempts = */ 1
+ /* failedAttempts = */ 1,
)
verify(view)
.showAlmostAtWipeDialog(
any(),
any(),
- eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER)
+ eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER),
)
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 54368ca..4b97745 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -73,12 +73,16 @@
private static final int MESSAGE_MOVE_MOUSE_POINTER = 1;
private static final int MESSAGE_SCROLL_MOUSE_POINTER = 2;
- private static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f;
private static final int KEY_NOT_SET = -1;
/** Time interval after which mouse action will be repeated */
private static final int INTERVAL_MILLIS = 10;
+ @VisibleForTesting
+ public static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f;
+ @VisibleForTesting
+ public static final float MOUSE_SCROLL_STEP = 0.2f;
+
private final AccessibilityManagerService mAms;
private final Handler mHandler;
private final InputManager mInputManager;
@@ -281,8 +285,8 @@
MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
float y = switch (mouseKeyEvent) {
- case UP_MOVE_OR_SCROLL -> 1.0f;
- case DOWN_MOVE_OR_SCROLL -> -1.0f;
+ case UP_MOVE_OR_SCROLL -> MOUSE_SCROLL_STEP;
+ case DOWN_MOVE_OR_SCROLL -> -MOUSE_SCROLL_STEP;
default -> 0.0f;
};
waitForVirtualMouseCreation();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 71cb882..e053964 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2207,7 +2207,7 @@
+ ", groupId=" + powerGroup.getGroupId()
+ ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
}
- if (mForceSuspendActive || !mSystemReady) {
+ if (mForceSuspendActive || !mSystemReady || (powerGroup == null)) {
return;
}
powerGroup.wakeUpLocked(eventTime, reason, details, uid, opPackageName, opUid,
@@ -6027,6 +6027,12 @@
@Override // Binder call
public void wakeUp(long eventTime, @WakeReason int reason, String details,
String opPackageName) {
+ wakeUpWithDisplayId(eventTime, reason, details, opPackageName, Display.DEFAULT_DISPLAY);
+ }
+
+ @Override // Binder call
+ public void wakeUpWithDisplayId(long eventTime, @WakeReason int reason, String details,
+ String opPackageName, int displayId) {
final long now = mClock.uptimeMillis();
if (eventTime > now) {
Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now);
@@ -6039,13 +6045,14 @@
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
+ int displayGroupId = getDisplayGroupId(displayId);
synchronized (mLock) {
if (!mBootCompleted && sQuiescent) {
mDirty |= DIRTY_QUIESCENT;
updatePowerStateLocked();
return;
}
- wakePowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), eventTime,
+ wakePowerGroupLocked(mPowerGroups.get(displayGroupId), eventTime,
reason, details, uid, opPackageName, uid);
}
} finally {
@@ -7335,4 +7342,12 @@
}
}
}
+
+ private int getDisplayGroupId(int displayId) {
+ DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
+ if (displayInfo == null) {
+ return Display.INVALID_DISPLAY_GROUP;
+ }
+ return displayInfo.displayGroupId;
+ }
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index b58c28b..54a02cf 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -268,6 +268,10 @@
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
+ DisplayInfo displayInfo = Mockito.mock(DisplayInfo.class);
+ displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY))
+ .thenReturn(displayInfo);
}
private PowerManagerService createService() {
@@ -794,6 +798,57 @@
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
}
+ @Test
+ public void testWakefulnessPerGroup_IPowerManagerWakeUpWithDisplayId() {
+ final int nonDefaultPowerGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ int displayInNonDefaultGroup = 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ long eventTime1 = 10;
+ long eventTime2 = eventTime1 + 1;
+ long eventTime3 = eventTime2 + 1;
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultPowerGroupId);
+
+ // Verify the global wakefulness is AWAKE
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Transition default display to doze, and verify the global wakefulness
+ mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_DOZING, eventTime1,
+ 0, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, 0, null, null);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Transition the display from non default power group to doze, and verify the change in
+ // the global wakefulness
+ mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_DOZING, eventTime2,
+ 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ assertThat(mService.getWakefulnessLocked(nonDefaultPowerGroupId))
+ .isEqualTo(WAKEFULNESS_DOZING);
+
+ // Wakeup the display from the non default power group
+ DisplayInfo displayInfo = Mockito.mock(DisplayInfo.class);
+ displayInfo.displayGroupId = nonDefaultPowerGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(displayInNonDefaultGroup))
+ .thenReturn(displayInfo);
+ mClock.fastForward(eventTime3);
+ mService.getBinderServiceInstance().wakeUpWithDisplayId(eventTime3,
+ PowerManager.WAKE_REASON_APPLICATION, "testing IPowerManager.wakeUp()",
+ "pkg.name", displayInNonDefaultGroup);
+
+ assertThat(mService.getWakefulnessLocked(nonDefaultPowerGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY))
+ .isEqualTo(WAKEFULNESS_DOZING);
+ }
+
/**
* Tests a series of variants that control whether a device wakes-up when it is plugged in
* or docked.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index 019ccf9..c76392b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -61,7 +61,6 @@
companion object {
const val DISPLAY_ID = 1
const val DEVICE_ID = 123
- const val MOUSE_POINTER_MOVEMENT_STEP = 1.8f
// This delay is required for key events to be sent and handled correctly.
// The handler only performs a move/scroll event if it receives the key event
// at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests.
@@ -159,8 +158,8 @@
testLooper.dispatchAll()
// Verify the sendRelativeEvent method is called once and capture the arguments
- verifyRelativeEvents(arrayOf(-MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)),
- arrayOf(MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)))
+ verifyRelativeEvents(arrayOf(-MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)),
+ arrayOf(MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)))
}
@Test
@@ -232,7 +231,8 @@
testLooper.dispatchAll()
// Verify the sendScrollEvent method is called once and capture the arguments
- verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+ verifyScrollEvents(arrayOf<Float>(0f),
+ arrayOf<Float>(MouseKeysInterceptor.MOUSE_SCROLL_STEP))
}
@Test
@@ -247,7 +247,8 @@
testLooper.dispatchAll()
// Verify the sendRelativeEvent method is called once and capture the arguments
- verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(-MOUSE_POINTER_MOVEMENT_STEP))
+ verifyRelativeEvents(arrayOf<Float>(0f),
+ arrayOf<Float>(-MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP))
}
private fun verifyRelativeEvents(expectedX: Array<Float>, expectedY: Array<Float>) {