summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt9
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/app/supervision/ISupervisionManager.aidl3
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java71
-rw-r--r--core/java/android/app/supervision/flags.aconfig8
-rw-r--r--core/java/android/content/Context.java4
-rw-r--r--core/java/android/view/Display.java1
-rw-r--r--core/java/android/view/NotificationHeaderView.java19
-rw-r--r--core/jni/android_media_AudioSystem.cpp12
-rw-r--r--core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp18
-rw-r--r--core/res/res/layout/notification_2025_conversation_header.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_base.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml1
-rw-r--r--core/res/res/values/config.xml9
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--graphics/java/android/graphics/FrameInfo.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt131
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt146
-rw-r--r--media/java/android/media/AudioSystem.java15
-rw-r--r--media/java/android/media/RingtoneManager.java20
-rw-r--r--media/java/android/media/Utils.java6
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SystemUI/OWNERS12
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java194
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml1
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid.xml1
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java122
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java25
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java73
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java4
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java29
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java4
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java40
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java500
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java68
62 files changed, 1448 insertions, 527 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index db701546ba62..0d5ec199b953 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2938,6 +2938,14 @@ package android.app.smartspace.uitemplatedata {
}
+package android.app.supervision {
+
+ @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
+ method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled();
+ }
+
+}
+
package android.app.time {
public final class Capabilities {
@@ -3801,6 +3809,7 @@ package android.content {
field public static final String SHARED_CONNECTIVITY_SERVICE = "shared_connectivity";
field public static final String SMARTSPACE_SERVICE = "smartspace";
field public static final String STATS_MANAGER = "stats";
+ field @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public static final String SUPERVISION_SERVICE = "supervision";
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 85b65bb6605e..975c2c27cb22 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -850,6 +850,14 @@ package android.app.prediction {
}
+package android.app.supervision {
+
+ @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
+ method public void setSupervisionEnabled(boolean);
+ }
+
+}
+
package android.app.usage {
public class StorageStatsManager {
diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl
index e583302e4d3b..2f67a8abcd17 100644
--- a/core/java/android/app/supervision/ISupervisionManager.aidl
+++ b/core/java/android/app/supervision/ISupervisionManager.aidl
@@ -16,11 +16,14 @@
package android.app.supervision;
+import android.content.Intent;
+
/**
* Internal IPC interface to the supervision service.
* {@hide}
*/
interface ISupervisionManager {
+ Intent createConfirmSupervisionCredentialsIntent();
boolean isSupervisionEnabledForUser(int userId);
void setSupervisionEnabledForUser(int userId, boolean enabled);
String getActiveSupervisionAppPackage(int userId);
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index d30705536045..0270edf080a9 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -16,13 +16,22 @@
package android.app.supervision;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_USERS;
+import static android.Manifest.permission.QUERY_USERS;
+
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
+import android.app.supervision.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.Intent;
import android.os.RemoteException;
/**
@@ -31,6 +40,8 @@ import android.os.RemoteException;
* @hide
*/
@SystemService(Context.SUPERVISION_SERVICE)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SUPERVISION_MANAGER_APIS)
public class SupervisionManager {
private final Context mContext;
@Nullable private final ISupervisionManager mService;
@@ -47,7 +58,8 @@ public class SupervisionManager {
*
* @hide
*/
- public static final String ACTION_ENABLE_SUPERVISION = "android.app.action.ENABLE_SUPERVISION";
+ public static final String ACTION_ENABLE_SUPERVISION =
+ "android.app.supervision.action.ENABLE_SUPERVISION";
/**
* Activity action: ask the human user to disable supervision for this user. Only the app that
@@ -62,7 +74,7 @@ public class SupervisionManager {
* @hide
*/
public static final String ACTION_DISABLE_SUPERVISION =
- "android.app.action.DISABLE_SUPERVISION";
+ "android.app.supervision.action.DISABLE_SUPERVISION";
/** @hide */
@UnsupportedAppUsage
@@ -72,11 +84,46 @@ public class SupervisionManager {
}
/**
+ * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
+ * launch the activity to verify supervision credentials.
+ *
+ * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this API
+ * is called, the launched activity still need to perform validity checks as the supervision
+ * state can change when the activity is launched. A null intent is returned if supervision is
+ * disabled at the time of this API call.
+ *
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
+ * of the supervision credentials.
+ *
+ * @hide
+ */
+ @RequiresPermission(value = android.Manifest.permission.QUERY_USERS)
+ @Nullable
+ public Intent createConfirmSupervisionCredentialsIntent() {
+ if (mService != null) {
+ try {
+ Intent result = mService.createConfirmSupervisionCredentialsIntent();
+ if (result != null) {
+ result.prepareToEnterProcess(
+ Intent.LOCAL_FLAG_FROM_SYSTEM, mContext.getAttributionSource());
+ }
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns whether the device is supervised.
*
* @hide
*/
- @UserHandleAware
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SUPERVISION_MANAGER_APIS)
+ @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public boolean isSupervisionEnabled() {
return isSupervisionEnabledForUser(mContext.getUserId());
}
@@ -84,14 +131,10 @@ public class SupervisionManager {
/**
* Returns whether the device is supervised.
*
- * <p>The caller must be from the same user as the target or hold the {@link
- * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
- *
* @hide
*/
- @RequiresPermission(
- value = android.Manifest.permission.INTERACT_ACROSS_USERS,
- conditional = true)
+ @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
if (mService != null) {
try {
@@ -108,7 +151,8 @@ public class SupervisionManager {
*
* @hide
*/
- @UserHandleAware
+ @TestApi
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public void setSupervisionEnabled(boolean enabled) {
setSupervisionEnabledForUser(mContext.getUserId(), enabled);
}
@@ -116,14 +160,9 @@ public class SupervisionManager {
/**
* Sets whether the device is supervised for a given user.
*
- * <p>The caller must be from the same user as the target or hold the {@link
- * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
- *
* @hide
*/
- @RequiresPermission(
- value = android.Manifest.permission.INTERACT_ACROSS_USERS,
- conditional = true)
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
if (mService != null) {
try {
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 232883cbfe00..94de03877fd7 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -64,3 +64,11 @@ flag {
description: "Flag that enables the Supervision pin recovery screen with Supervision settings entry point"
bug: "390500290"
}
+
+flag {
+ name: "supervision_manager_apis"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that enables system APIs in Supervision Manager"
+ bug: "382034839"
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3391e79b2ae4..55d78f9b8c48 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -17,8 +17,8 @@
package android.content;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
-import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
+import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
import android.annotation.AttrRes;
@@ -6858,6 +6858,8 @@ public abstract class Context {
* @see android.app.supervision.SupervisionManager
* @hide
*/
+ @SystemApi
+ @FlaggedApi(android.app.supervision.flags.Flags.FLAG_SUPERVISION_MANAGER_APIS)
public static final String SUPERVISION_SERVICE = "supervision";
/**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index ca0959af3ff8..231aa6816908 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1599,7 +1599,6 @@ public final class Display {
mGlobal.registerDisplayListener(toRegister, executor,
DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
ActivityThread.currentPackageName());
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 73cd5ecd39ef..df680c054f56 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -32,7 +32,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
@@ -266,20 +265,14 @@ public class NotificationHeaderView extends RelativeLayout {
? R.style.TextAppearance_DeviceDefault_Notification_Title
: R.style.TextAppearance_DeviceDefault_Notification_Info;
// Most of the time, we're showing text in the minimized state
- if (findViewById(R.id.header_text) instanceof TextView headerText) {
- headerText.setTextAppearance(styleResId);
- if (notificationsRedesignTemplates()) {
- // TODO: b/378660052 - When inlining the redesign flag, this should be updated
- // directly in TextAppearance_DeviceDefault_Notification_Title so we won't need to
- // override it here.
- float textSize = getContext().getResources().getDimension(
- R.dimen.notification_2025_title_text_size);
- headerText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
- }
+ View headerText = findViewById(R.id.header_text);
+ if (headerText instanceof TextView) {
+ ((TextView) headerText).setTextAppearance(styleResId);
}
// If there's no summary or text, we show the app name instead of nothing
- if (findViewById(R.id.app_name_text) instanceof TextView appNameText) {
- appNameText.setTextAppearance(styleResId);
+ View appNameText = findViewById(R.id.app_name_text);
+ if (appNameText instanceof TextView) {
+ ((TextView) appNameText).setTextAppearance(styleResId);
}
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 2ba6bc4912c3..b679688959b1 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -664,14 +664,16 @@ static void android_media_AudioSystem_vol_range_init_req_callback()
static jint android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz,
jint state, jobject jParcel,
- jint codec) {
+ jint codec, jboolean deviceSwitch) {
int status;
if (Parcel *parcel = parcelForJavaObject(env, jParcel); parcel != nullptr) {
android::media::audio::common::AudioPort port{};
if (status_t statusOfParcel = port.readFromParcel(parcel); statusOfParcel == OK) {
- status = check_AudioSystem_Command(
- AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(state),
- port, static_cast<audio_format_t>(codec)));
+ status = check_AudioSystem_Command(
+ AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(
+ state),
+ port, static_cast<audio_format_t>(codec),
+ deviceSwitch));
} else {
ALOGE("Failed to read from parcel: %s", statusToString(statusOfParcel).c_str());
status = kAudioStatusError;
@@ -3457,7 +3459,7 @@ static const JNINativeMethod gMethods[] = {
MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId),
MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId),
MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId),
- MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
+ MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;IZ)I",
android_media_AudioSystem_setDeviceConnectionState),
MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState),
MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange),
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index e0cc055a62a6..c4259f41e380 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -266,16 +266,24 @@ class NativeCommandBuffer {
}
// Picky version of atoi(). No sign or unexpected characters allowed. Return -1 on failure.
static int digitsVal(char* start, char* end) {
+ constexpr int vmax = std::numeric_limits<int>::max();
int result = 0;
- if (end - start > 6) {
- return -1;
- }
for (char* dp = start; dp < end; ++dp) {
if (*dp < '0' || *dp > '9') {
- ALOGW("Argument failed integer format check");
+ ALOGW("Argument contains non-integer characters");
+ return -1;
+ }
+ int digit = *dp - '0';
+ if (result > vmax / 10) {
+ ALOGW("Argument exceeds int limit");
+ return -1;
+ }
+ result *= 10;
+ if (result > vmax - digit) {
+ ALOGW("Argument exceeds int limit");
return -1;
}
- result = 10 * result + (*dp - '0');
+ result += digit;
}
return result;
}
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 75bd244cbbf4..1bde17358825 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
+ android:textSize="16sp"
android:singleLine="true"
android:layout_weight="1"
/>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index 054583297d37..d29b7af9e24e 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -102,7 +102,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 9959b666b3bf..5beab508aecf 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -104,7 +104,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index 85ca124de8ff..d7c3263904d4 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -130,7 +130,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
index 11fc48668ad7..52bc7b8ea3bb 100644
--- a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
@@ -69,7 +69,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
index bf70a5eff47e..cf9ff6bef6f8 100644
--- a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
@@ -90,7 +90,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a311d572e0b..2188469bdf03 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2643,6 +2643,15 @@
<!-- MMS user agent prolfile url -->
<string name="config_mms_user_agent_profile_url" translatable="false"></string>
+ <!-- The default list of possible CMF Names|Style|ColorSource. This array can be
+ overridden device-specific resources. A wildcard (fallback) must be supplied.
+ Name - Read from `ro.boot.hardware.color` sysprop. Fallback (*) required.
+ Styles - frameworks/libs/systemui/monet/src/com/android/systemui/monet/Style.java
+ Color - `home_wallpaper` (for color extraction) or a hexadecimal int (#FFcc99) -->
+ <string-array name="theming_defaults">
+ <item>*|TONAL_SPOT|home_wallpaper</item>
+ </string-array>
+
<!-- National Language Identifier codes for the following two config items.
(from 3GPP TS 23.038 V9.1.1 Table 6.2.1.2.4.1):
0 - reserved
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e9d87e4b5f5b..9acb2427aaab 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -580,9 +580,6 @@
<dimen name="notification_text_size">14sp</dimen>
<!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
<dimen name="notification_title_text_size">14sp</dimen>
- <!-- Size of notification text titles, 2025 redesign version (see TextAppearance.StatusBar.EventContent.Title) -->
- <!-- TODO: b/378660052 - When inlining the redesign flag, this should be updated directly in TextAppearance.DeviceDefault.Notification.Title -->
- <dimen name="notification_2025_title_text_size">16sp</dimen>
<!-- Size of big notification text titles (see TextAppearance.StatusBar.EventContent.BigTitle) -->
<dimen name="notification_big_title_text_size">16sp</dimen>
<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c62732d36038..ffcfce9c420e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -575,7 +575,6 @@
<java-symbol type="dimen" name="notification_text_size" />
<java-symbol type="dimen" name="notification_title_text_size" />
<java-symbol type="dimen" name="notification_subtext_size" />
- <java-symbol type="dimen" name="notification_2025_title_text_size" />
<java-symbol type="dimen" name="notification_top_pad" />
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
@@ -5904,6 +5903,9 @@
<java-symbol type="drawable" name="ic_notification_summarization" />
<java-symbol type="dimen" name="notification_collapsed_height_with_summarization" />
+ <!-- Device CMF Theming Settings -->
+ <java-symbol type="array" name="theming_defaults" />
+
<!-- Advanced Protection Service USB feature -->
<java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_title" />
<java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_text" />
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 7d236d203201..3b8f46630344 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -93,10 +93,12 @@ public final class FrameInfo {
// Interval between two consecutive frames
public static final int FRAME_INTERVAL = 11;
+ // Workload target deadline for a frame
+ public static final int WORKLOAD_TARGET = 12;
+
// Must be the last one
// This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
- // In calculating size, + 1 for Flags, and + 1 for WorkloadTarget from FrameInfo.h
- private static final int FRAME_INFO_SIZE = FRAME_INTERVAL + 2;
+ private static final int FRAME_INFO_SIZE = WORKLOAD_TARGET + 1;
/** checkstyle */
public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
@@ -108,6 +110,7 @@ public final class FrameInfo {
frameInfo[FRAME_DEADLINE] = frameDeadline;
frameInfo[FRAME_START_TIME] = frameStartTime;
frameInfo[FRAME_INTERVAL] = frameInterval;
+ frameInfo[WORKLOAD_TARGET] = frameDeadline - intendedVsync;
}
/** checkstyle */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt
new file mode 100644
index 000000000000..a1d700af5569
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.dagger.WMSingleton
+import java.util.function.Supplier
+import javax.inject.Inject
+
+/**
+ * An Injectable [Supplier<WindowContainerTransaction>]. This can be used in place of kotlin default
+ * parameters values [builder = ::WindowContainerTransaction] which requires the
+ * [@JvmOverloads] annotation to make this available in Java.
+ * This can be used every time a component needs the dependency to the default [Supplier] for
+ * [WindowContainerTransaction]s.
+ */
+@WMSingleton
+class WindowContainerTransactionSupplier @Inject constructor(
+) : Supplier<WindowContainerTransaction> {
+ override fun get(): WindowContainerTransaction = WindowContainerTransaction()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt
new file mode 100644
index 000000000000..bdffcf51e7d4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.graphics.Rect
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.window.WindowContainerToken
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY
+
+/**
+ * [GestureDetector.SimpleOnGestureListener] implementation which receives events from the
+ * Letterbox Input surface, understands the type of event and filter them based on the current
+ * letterbox position.
+ */
+class ReachabilityGestureListener(
+ private val taskId: Int,
+ private val token: WindowContainerToken?,
+ private val transitions: Transitions,
+ private val animationHandler: Transitions.TransitionHandler,
+ private val wctSupplier: WindowContainerTransactionSupplier
+) : GestureDetector.SimpleOnGestureListener() {
+
+ // The current letterbox bounds. Double tap events are ignored when happening in these bounds.
+ private val activityBounds = Rect()
+
+ override fun onDoubleTap(e: MotionEvent): Boolean {
+ val x = e.rawX.toInt()
+ val y = e.rawY.toInt()
+ if (!activityBounds.contains(x, y)) {
+ val wct = wctSupplier.get().apply {
+ setReachabilityOffset(token!!, taskId, x, y)
+ }
+ transitions.startTransition(
+ TRANSIT_MOVE_LETTERBOX_REACHABILITY,
+ wct,
+ animationHandler
+ )
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Updates the bounds for the letterboxed activity.
+ */
+ fun updateActivityBounds(newActivityBounds: Rect) {
+ activityBounds.set(newActivityBounds)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt
new file mode 100644
index 000000000000..5e9fe09bc840
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.window.WindowContainerToken
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.transition.Transitions
+import javax.inject.Inject
+
+/**
+ * A Factory for [ReachabilityGestureListener].
+ */
+@WMSingleton
+class ReachabilityGestureListenerFactory @Inject constructor(
+ private val transitions: Transitions,
+ private val animationHandler: Transitions.TransitionHandler,
+ private val wctSupplier: WindowContainerTransactionSupplier
+) {
+ /**
+ * @return a [ReachabilityGestureListener] implementation to listen to double tap events and
+ * creating the related [WindowContainerTransaction] to handle the transition.
+ */
+ fun createReachabilityGestureListener(
+ taskId: Int,
+ token: WindowContainerToken?
+ ): ReachabilityGestureListener =
+ ReachabilityGestureListener(taskId, token, transitions, animationHandler, wctSupplier)
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt
new file mode 100644
index 000000000000..c91ef5e6b868
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [WindowContainerTransactionSupplier].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:WindowContainerTransactionSupplierTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class WindowContainerTransactionSupplierTest {
+
+ @Test
+ fun `WindowContainerTransactionSupplier supplies a WindowContainerTransaction`() {
+ val supplier = WindowContainerTransactionSupplier()
+ SuppliersUtilsTest.assertSupplierProvidesValue(supplier) {
+ it is WindowContainerTransaction
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt
new file mode 100644
index 000000000000..a5f6ced20dc0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.compatui.letterbox.LetterboxEvents.motionEventAt
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [ReachabilityGestureListenerFactory].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityGestureListenerFactoryTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ReachabilityGestureListenerFactoryTest : ShellTestCase() {
+
+ @Test
+ fun `When invoked a ReachabilityGestureListenerFactory is created`() {
+ runTestScenario { r ->
+ r.invokeCreate()
+
+ r.checkReachabilityGestureListenerCreated()
+ }
+ }
+
+ @Test
+ fun `Right parameters are used for creation`() {
+ runTestScenario { r ->
+ r.invokeCreate()
+
+ r.checkRightParamsAreUsed()
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<ReachabilityGestureListenerFactoryRobotTest>) {
+ val robot = ReachabilityGestureListenerFactoryRobotTest()
+ consumer.accept(robot)
+ }
+
+ class ReachabilityGestureListenerFactoryRobotTest {
+
+ companion object {
+ @JvmStatic
+ private val TASK_ID = 1
+
+ @JvmStatic
+ private val TOKEN = mock<WindowContainerToken>()
+ }
+
+ private val transitions: Transitions
+ private val animationHandler: Transitions.TransitionHandler
+ private val factory: ReachabilityGestureListenerFactory
+ private val wctSupplier: WindowContainerTransactionSupplier
+ private val wct: WindowContainerTransaction
+ private lateinit var obtainedResult: Any
+
+ init {
+ transitions = mock<Transitions>()
+ animationHandler = mock<Transitions.TransitionHandler>()
+ wctSupplier = mock<WindowContainerTransactionSupplier>()
+ wct = mock<WindowContainerTransaction>()
+ doReturn(wct).`when`(wctSupplier).get()
+ factory = ReachabilityGestureListenerFactory(transitions, animationHandler, wctSupplier)
+ }
+
+ fun invokeCreate(taskId: Int = TASK_ID, token: WindowContainerToken? = TOKEN) {
+ obtainedResult = factory.createReachabilityGestureListener(taskId, token)
+ }
+
+ fun checkReachabilityGestureListenerCreated(expected: Boolean = true) {
+ assertEquals(expected, obtainedResult is ReachabilityGestureListener)
+ }
+
+ fun checkRightParamsAreUsed(taskId: Int = TASK_ID, token: WindowContainerToken? = TOKEN) {
+ with(obtainedResult as ReachabilityGestureListener) {
+ // Click outside the bounds
+ updateActivityBounds(Rect(0, 0, 10, 20))
+ onDoubleTap(motionEventAt(50f, 100f))
+ // WindowContainerTransactionSupplier is invoked to create a
+ // WindowContainerTransaction
+ verify(wctSupplier).get()
+ // Verify the right params are passed to startAppCompatReachability()
+ verify(wct).setReachabilityOffset(
+ token!!,
+ taskId,
+ 50,
+ 100
+ )
+ // startTransition() is invoked on Transitions with the right parameters
+ verify(transitions).startTransition(
+ TRANSIT_MOVE_LETTERBOX_REACHABILITY,
+ wct,
+ animationHandler
+ )
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt
new file mode 100644
index 000000000000..bc10ea578ffb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.compatui.letterbox.LetterboxEvents.motionEventAt
+import com.android.wm.shell.compatui.letterbox.asMode
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [ReachabilityGestureListener].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityGestureListenerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ReachabilityGestureListenerTest : ShellTestCase() {
+
+ @Test
+ fun `Only events outside the bounds are handled`() {
+ runTestScenario { r ->
+ r.updateActivityBounds(Rect(0, 0, 100, 200))
+ r.sendMotionEvent(50, 100)
+
+ r.verifyReachabilityTransitionCreated(expected = false, 50, 100)
+ r.verifyReachabilityTransitionStarted(expected = false)
+ r.verifyEventIsHandled(expected = false)
+
+ r.updateActivityBounds(Rect(0, 0, 10, 50))
+ r.sendMotionEvent(50, 100)
+
+ r.verifyReachabilityTransitionCreated(expected = true, 50, 100)
+ r.verifyReachabilityTransitionStarted(expected = true)
+ r.verifyEventIsHandled(expected = true)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<ReachabilityGestureListenerRobotTest>) {
+ val robot = ReachabilityGestureListenerRobotTest()
+ consumer.accept(robot)
+ }
+
+ class ReachabilityGestureListenerRobotTest(
+ taskId: Int = TASK_ID,
+ token: WindowContainerToken? = TOKEN
+ ) {
+
+ companion object {
+ @JvmStatic
+ private val TASK_ID = 1
+
+ @JvmStatic
+ private val TOKEN = mock<WindowContainerToken>()
+ }
+
+ private val reachabilityListener: ReachabilityGestureListener
+ private val transitions: Transitions
+ private val animationHandler: Transitions.TransitionHandler
+ private val wctSupplier: WindowContainerTransactionSupplier
+ private val wct: WindowContainerTransaction
+ private var eventHandled = false
+
+ init {
+ transitions = mock<Transitions>()
+ animationHandler = mock<Transitions.TransitionHandler>()
+ wctSupplier = mock<WindowContainerTransactionSupplier>()
+ wct = mock<WindowContainerTransaction>()
+ doReturn(wct).`when`(wctSupplier).get()
+ reachabilityListener =
+ ReachabilityGestureListener(
+ taskId,
+ token,
+ transitions,
+ animationHandler,
+ wctSupplier
+ )
+ }
+
+ fun updateActivityBounds(activityBounds: Rect) {
+ reachabilityListener.updateActivityBounds(activityBounds)
+ }
+
+ fun sendMotionEvent(x: Int, y: Int) {
+ eventHandled = reachabilityListener.onDoubleTap(motionEventAt(x.toFloat(), y.toFloat()))
+ }
+
+ fun verifyReachabilityTransitionCreated(
+ expected: Boolean,
+ x: Int,
+ y: Int,
+ taskId: Int = TASK_ID,
+ token: WindowContainerToken? = TOKEN
+ ) {
+ verify(wct, expected.asMode()).setReachabilityOffset(
+ token!!,
+ taskId,
+ x,
+ y
+ )
+ }
+
+ fun verifyReachabilityTransitionStarted(expected: Boolean = true) {
+ verify(transitions, expected.asMode()).startTransition(
+ TRANSIT_MOVE_LETTERBOX_REACHABILITY,
+ wct,
+ animationHandler
+ )
+ }
+
+ fun verifyEventIsHandled(expected: Boolean) {
+ assertEquals(expected, eventHandled)
+ }
+ }
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 12d7f33a0d51..e01cb928e369 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1754,13 +1754,21 @@ public class AudioSystem
@UnsupportedAppUsage
public static int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
int codecFormat) {
+ return setDeviceConnectionState(attributes, state, codecFormat, false /*deviceSwitch*/);
+ }
+
+ /**
+ * @hide
+ */
+ public static int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
+ int codecFormat, boolean deviceSwitch) {
android.media.audio.common.AudioPort port =
AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort(attributes);
Parcel parcel = Parcel.obtain();
port.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
try {
- return setDeviceConnectionState(state, parcel, codecFormat);
+ return setDeviceConnectionState(state, parcel, codecFormat, deviceSwitch);
} finally {
parcel.recycle();
}
@@ -1769,7 +1777,10 @@ public class AudioSystem
* @hide
*/
@UnsupportedAppUsage
- public static native int setDeviceConnectionState(int state, Parcel parcel, int codecFormat);
+ public static native int setDeviceConnectionState(int state, Parcel parcel, int codecFormat,
+ boolean deviceSwitch);
+
+
/** @hide */
@UnsupportedAppUsage
public static native int getDeviceConnectionState(int device, String device_address);
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 0f24654879cd..021348153bb8 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -60,6 +60,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* RingtoneManager provides access to ringtones, notification, and other types
@@ -810,9 +811,7 @@ public class RingtoneManager {
// Don't set the stream type
Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
volumeShaperConfig, false);
- if (Flags.enableRingtoneHapticsCustomization()
- && Utils.isRingtoneVibrationSettingsSupported(context)
- && Utils.hasVibration(ringtoneUri) && hasHapticChannels(ringtoneUri)) {
+ if (muteHapticChannelForVibration(context, ringtoneUri)) {
audioAttributes = new AudioAttributes.Builder(
audioAttributes).setHapticChannelsMuted(true).build();
}
@@ -1305,4 +1304,19 @@ public class RingtoneManager {
default: throw new IllegalArgumentException();
}
}
+
+ private static boolean muteHapticChannelForVibration(Context context, Uri ringtoneUri) {
+ final Uri vibrationUri = Utils.getVibrationUri(ringtoneUri);
+ // No vibration is specified
+ if (vibrationUri == null) {
+ return false;
+ }
+ // The user specified the synchronized pattern
+ if (Objects.equals(vibrationUri.toString(), Utils.SYNCHRONIZED_VIBRATION)) {
+ return false;
+ }
+ return Flags.enableRingtoneHapticsCustomization()
+ && Utils.isRingtoneVibrationSettingsSupported(context)
+ && hasHapticChannels(ringtoneUri);
+ }
}
diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java
index 11bd221ec696..d6e27b0ffa75 100644
--- a/media/java/android/media/Utils.java
+++ b/media/java/android/media/Utils.java
@@ -66,6 +66,8 @@ public class Utils {
public static final String VIBRATION_URI_PARAM = "vibration_uri";
+ public static final String SYNCHRONIZED_VIBRATION = "synchronized";
+
/**
* Sorts distinct (non-intersecting) range array in ascending order.
* @throws java.lang.IllegalArgumentException if ranges are not distinct
@@ -757,8 +759,8 @@ public class Utils {
return null;
}
String filePath = vibrationUri.getPath();
- if (filePath == null) {
- Log.w(TAG, "The file path is null.");
+ if (filePath == null || filePath.equals(Utils.SYNCHRONIZED_VIBRATION)) {
+ Log.w(TAG, "Ignore the vibration parsing for file:" + filePath);
return null;
}
File vibrationFile = new File(filePath);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4b0400fb3441..91ec83690722 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -889,6 +889,9 @@
<!-- Preference category for monitoring debugging development settings. [CHAR LIMIT=25] -->
<string name="debug_monitoring_category">Monitoring</string>
+ <!-- Preference category to alter window management settings, [CHAR LIMIT=50] -->
+ <string name="window_management_category">Window Management</string>
+
<!-- UI debug setting: always enable strict mode? [CHAR LIMIT=25] -->
<string name="strict_mode">Strict mode enabled</string>
<!-- UI debug setting: show strict mode summary [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 236654deefb5..f5c0233d56b1 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -8,7 +8,6 @@ achalke@google.com
acul@google.com
adamcohen@google.com
aioana@google.com
-alexchau@google.com
alexflo@google.com
andonian@google.com
amiko@google.com
@@ -91,10 +90,8 @@ rahulbanerjee@google.com
rgl@google.com
roosa@google.com
saff@google.com
-samcackett@google.com
santie@google.com
shanh@google.com
-silvajordan@google.com
snoeberger@google.com
spdonghao@google.com
steell@google.com
@@ -106,7 +103,6 @@ thiruram@google.com
tracyzhou@google.com
tsuji@google.com
twickham@google.com
-uwaisashraf@google.com
vadimt@google.com
valiiftime@google.com
vanjan@google.com
@@ -121,3 +117,11 @@ yuandizhou@google.com
yurilin@google.com
yuzhechen@google.com
zakcohen@google.com
+
+# Overview eng team
+alexchau@google.com
+samcackett@google.com
+silvajordan@google.com
+uwaisashraf@google.com
+vinayjoglekar@google.com
+willosborn@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3e241bfe6447..082fb5108f8e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1977,6 +1977,16 @@ flag {
}
flag {
+ name: "hardware_color_styles"
+ namespace: "systemui"
+ description: "Enables loading initial colors based ion hardware color"
+ bug: "347286986"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "shade_launch_accessibility"
namespace: "systemui"
description: "Intercept accessibility focus events for the Shade during launch animations to avoid stray TalkBack events."
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index bd811814eb24..4140a956182c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -18,9 +18,7 @@ package com.android.keyguard;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -28,8 +26,6 @@ import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricSourceType;
import android.testing.TestableLooper;
-import android.text.Editable;
-import android.text.TextWatcher;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -99,19 +95,6 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
}
@Test
- public void textChanged_AnnounceForAccessibility() {
- ArgumentCaptor<TextWatcher> textWatcherArgumentCaptor = ArgumentCaptor.forClass(
- TextWatcher.class);
- mMessageAreaController.onViewAttached();
- verify(mKeyguardMessageArea).addTextChangedListener(textWatcherArgumentCaptor.capture());
-
- textWatcherArgumentCaptor.getValue().afterTextChanged(
- Editable.Factory.getInstance().newEditable("abc"));
- verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class));
- verify(mKeyguardMessageArea).postDelayed(any(Runnable.class), anyLong());
- }
-
- @Test
public void testSetBouncerVisible() {
mMessageAreaController.setIsVisible(true);
verify(mKeyguardMessageArea).setIsVisible(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java
new file mode 100644
index 000000000000..ecd04a47b8ae
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 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.theme;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+
+public class HardwareColorRule implements TestRule {
+ public String color = "";
+ public String[] options = {};
+ public boolean isTesting = false;
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ HardwareColors hardwareColors = description.getAnnotation(HardwareColors.class);
+ if (hardwareColors != null) {
+ color = hardwareColors.color();
+ options = hardwareColors.options();
+ isTesting = true;
+ }
+ return base;
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java
new file mode 100644
index 000000000000..0b8df2e2670e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2025 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.theme;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface HardwareColors {
+ String color();
+ String[] options();
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 5cd0846ded7e..9a0b8125fb25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -64,6 +64,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.monet.DynamicColors;
@@ -77,6 +78,7 @@ import com.android.systemui.util.settings.SecureSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -98,6 +100,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
private static final UserHandle MANAGED_USER_HANDLE = UserHandle.of(100);
private static final UserHandle PRIVATE_USER_HANDLE = UserHandle.of(101);
+ @Rule
+ public HardwareColorRule rule = new HardwareColorRule();
+
@Mock
private JavaAdapter mJavaAdapter;
@Mock
@@ -148,13 +153,17 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<ContentObserver> mSettingsObserver;
+ @Mock
+ private SystemPropertiesHelper mSystemProperties;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+
when(mFeatureFlags.isEnabled(Flags.MONET)).thenReturn(true);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
when(mUiModeManager.getContrast()).thenReturn(0.5f);
- when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+
when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
.thenReturn(Color.RED);
when(mResources.getColor(eq(android.R.color.system_accent2_500), any()))
@@ -166,11 +175,20 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
when(mResources.getColor(eq(android.R.color.system_neutral2_500), any()))
.thenReturn(Color.BLACK);
+ when(mResources.getStringArray(com.android.internal.R.array.theming_defaults))
+ .thenReturn(rule.options);
+
+ // should fallback to `*|TONAL_SPOT|home_wallpaper`
+ when(mSystemProperties.get("ro.boot.hardware.color")).thenReturn(rule.color);
+ // will try set hardware colors as boot ONLY if user is not set yet
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(!rule.isTesting);
+
mThemeOverlayController = new ThemeOverlayController(mContext,
mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager,
+ mSystemProperties) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -214,11 +232,58 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
public void start_checksWallpaper() {
ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
verify(mBgExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+ }
+
+ @Test
+ @HardwareColors(color = "BLK", options = {
+ "BLK|MONOCHROMATIC|#FF0000",
+ "*|VIBRANT|home_wallpaper"
+ })
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_checkHardwareColor() {
+ // getWallpaperColors should not be called
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager, never()).getWallpaperColors(anyInt());
+
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.MONOCHROMATIC);
+ assertThat(mThemeOverlayController.mCurrentColors.get(0).getMainColors().get(
+ 0).toArgb()).isEqualTo(Color.RED);
+ }
+
+ @Test
+ @HardwareColors(color = "", options = {
+ "BLK|MONOCHROMATIC|#FF0000",
+ "*|VIBRANT|home_wallpaper"
+ })
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_wildcardColor() {
+ // getWallpaperColors will be called because we srt wildcard to `home_wallpaper`
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.VIBRANT);
+ }
+
+ @Test
+ @HardwareColors(color = "NONEXISTENT", options = {})
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_fallbackColor() {
+ // getWallpaperColors will be called because we default color source is `home_wallpaper`
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
registrationRunnable.getValue().run();
verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.TONAL_SPOT);
}
+
@Test
public void onWallpaperColorsChanged_setsTheme_whenForeground() {
// Should ask for a new theme when wallpaper colors change
@@ -287,9 +352,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"preset\"}";
+ String jsonString = createJsonString(TestColorSource.preset, "override.package.name",
+ "TONAL_SPOT");
+
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -313,11 +378,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -348,11 +409,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -381,11 +438,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.lock_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -404,11 +457,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.lock_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -455,8 +504,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
@Test
public void onSettingChanged_invalidStyle() {
when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true);
- String jsonString = "{\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.theme_style\":\"some_invalid_name\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper, "A16B00",
+ "some_invalid_name");
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -473,11 +522,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -506,11 +551,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -537,11 +578,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -570,11 +607,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -599,7 +632,6 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
-
@Test
@EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
@@ -608,11 +640,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(0xffa16b00), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -642,11 +670,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -676,11 +700,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -711,11 +731,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(0xffa16b00), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -745,11 +761,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -886,7 +898,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager,
+ mSystemProperties) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -926,7 +939,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager,
+ mSystemProperties) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -992,7 +1006,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
clearInvocations(mThemeOverlayApplier);
// Device went to sleep and second set of colors was applied.
- mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
+ mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
Color.valueOf(Color.RED), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
@@ -1018,7 +1032,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
clearInvocations(mThemeOverlayApplier);
// Device went to sleep and second set of colors was applied.
- mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
+ mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
Color.valueOf(Color.RED), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
@@ -1034,8 +1048,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.system_palette\":\"00FF00\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper, "00FF00",
+ "TONAL_SPOT");
+
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -1115,4 +1130,25 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
+ DynamicColors.getCustomColorsMapped(false).size() * 2)
).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
}
+
+ private enum TestColorSource {
+ preset,
+ home_wallpaper,
+ lock_wallpaper
+ }
+
+ private String createJsonString(TestColorSource colorSource, String seedColorHex,
+ String style) {
+ return "{\"android.theme.customization.color_source\":\"" + colorSource.toString() + "\","
+ + "\"android.theme.customization.system_palette\":\"" + seedColorHex + "\","
+ + "\"android.theme.customization.accent_color\":\"" + seedColorHex + "\","
+ + "\"android.theme.customization.color_index\":\"2\","
+ + "\"android.theme.customization.theme_style\":\"" + style + "\"}";
+ }
+
+ private String createJsonString(TestColorSource colorSource) {
+ return createJsonString(colorSource, "A16B00", "TONAL_SPOT");
+ }
+
+
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 0b35559148af..87d06bfde743 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
- android:importantForAccessibility="no"
+ android:accessibilityLiveRegion="polite"
android:ellipsize="marquee"
android:focusable="false"
android:gravity="center"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
index f231df2f1a10..c7f320c69113 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -67,6 +67,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 04457229d573..9359838238af 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -32,6 +32,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
index b184344f2f24..6cbe96a8cb50 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -68,6 +68,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 0e15ff66f3ee..cf388875a174 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -36,6 +36,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
index f6ac02aee657..33eab179c3f7 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -75,6 +75,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index ba4da794d777..fd5eeb8b9408 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -33,6 +33,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid.xml b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
index 8fd10fb3ddb8..8c34cd4165e0 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
@@ -29,7 +29,6 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@*android:dimen/notification_2025_title_text_size"
android:paddingEnd="4dp"
/>
<TextView
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index 35f2ef901bdd..a338e4c70cfa 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -54,7 +54,6 @@
android:singleLine="true"
android:paddingEnd="4dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@*android:dimen/notification_2025_title_text_size"
/>
<TextView
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index f528ec8af134..860a496ef18b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -20,22 +20,16 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.hardware.biometrics.BiometricSourceType;
import android.os.SystemClock;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.ViewController;
-import java.lang.ref.WeakReference;
-
import javax.inject.Inject;
/**
@@ -54,39 +48,8 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
private Pair<BiometricSourceType, Long> mMessageBiometricSource = null;
private static final Long SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS = 3500L;
- /**
- * Delay before speaking an accessibility announcement. Used to prevent
- * lift-to-type from interrupting itself.
- */
- private static final long ANNOUNCEMENT_DELAY = 250;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
- private final AnnounceRunnable mAnnounceRunnable;
- private final TextWatcher mTextWatcher = new TextWatcher() {
- @Override
- public void afterTextChanged(Editable editable) {
- CharSequence msg = editable;
- if (!TextUtils.isEmpty(msg)) {
- mView.removeCallbacks(mAnnounceRunnable);
- mAnnounceRunnable.setTextToAnnounce(msg);
- mView.postDelayed(() -> {
- if (msg == mView.getText()) {
- mAnnounceRunnable.run();
- }
- }, ANNOUNCEMENT_DELAY);
- }
- }
-
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- /* no-op */
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- /* no-op */
- }
- };
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
@@ -122,7 +85,6 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
- mAnnounceRunnable = new AnnounceRunnable(mView);
}
@Override
@@ -131,14 +93,12 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
mView.onThemeChanged();
- mView.addTextChangedListener(mTextWatcher);
}
@Override
protected void onViewDetached() {
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
- mView.removeTextChangedListener(mTextWatcher);
}
/**
@@ -232,30 +192,4 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
view, mKeyguardUpdateMonitor, mConfigurationController);
}
}
-
- /**
- * Runnable used to delay accessibility announcements.
- */
- @VisibleForTesting
- public static class AnnounceRunnable implements Runnable {
- private final WeakReference<View> mHost;
- private CharSequence mTextToAnnounce;
-
- AnnounceRunnable(View host) {
- mHost = new WeakReference<>(host);
- }
-
- /** Sets the text to announce. */
- public void setTextToAnnounce(CharSequence textToAnnounce) {
- mTextToAnnounce = textToAnnounce;
- }
-
- @Override
- public void run() {
- final View host = mHost.get();
- if (host != null && host.isVisibleToUser()) {
- host.announceForAccessibility(mTextToAnnounce);
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 434a9ce58c3b..7d8945a5b4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -191,7 +191,6 @@ object KeyguardBouncerViewBinder {
.filter { it == EXPANSION_VISIBLE }
.collect {
securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
- view.announceForAccessibility(securityContainerController.title)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
index c23ff5302b3c..dc444ffc2a34 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.shared.flag
+import android.window.DesktopExperienceFlags
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
@@ -30,10 +31,26 @@ object ShadeWindowGoesAround {
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
+ /**
+ * This is defined as [DesktopExperienceFlags] to make it possible to enable it together with
+ * all the other desktop experience flags from the dev settings.
+ *
+ * Alternatively, using adb:
+ * ```bash
+ * adb shell aflags enable com.android.window.flags.show_desktop_experience_dev_option && \
+ * adb shell setprop persist.wm.debug.desktop_experience_devopts 1
+ * ```
+ */
+ val FLAG =
+ DesktopExperienceFlags.DesktopExperienceFlag(
+ Flags::shadeWindowGoesAround,
+ /* shouldOverrideByDevOption= */ true,
+ )
+
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled: Boolean
- get() = Flags.shadeWindowGoesAround()
+ get() = FLAG.isTrue
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 28cf78f6777e..9f60fe212567 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,8 +18,10 @@ package com.android.systemui.theme;
import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
+import static com.android.systemui.Flags.hardwareColorStyles;
import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+import static com.android.systemui.monet.ColorScheme.GOOGLE_BLUE;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
@@ -73,6 +75,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -99,6 +102,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -136,9 +140,11 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
private final DeviceProvisionedController mDeviceProvisionedController;
private final Resources mResources;
// Current wallpaper colors associated to a user.
- private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
+ @VisibleForTesting
+ protected final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
private final WallpaperManager mWallpaperManager;
private final ActivityManager mActivityManager;
+ protected final SystemPropertiesHelper mSystemPropertiesHelper;
@VisibleForTesting
protected ColorScheme mColorScheme;
// If fabricated overlays were already created for the current theme.
@@ -423,7 +429,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
JavaAdapter javaAdapter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
UiModeManager uiModeManager,
- ActivityManager activityManager) {
+ ActivityManager activityManager,
+ SystemPropertiesHelper systemPropertiesHelper
+ ) {
mContext = context;
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY);
@@ -443,6 +451,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mUiModeManager = uiModeManager;
mActivityManager = activityManager;
+ mSystemPropertiesHelper = systemPropertiesHelper;
dumpManager.registerDumpable(TAG, this);
Flow<Boolean> isFinishedInAsleepStateFlow = mKeyguardTransitionInteractor
@@ -498,29 +507,38 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+ WallpaperColors systemColor;
+ if (hardwareColorStyles() && !mDeviceProvisionedController.isCurrentUserSetup()) {
+ Pair<Integer, Color> defaultSettings = getThemeSettingsDefaults();
+ mThemeStyle = defaultSettings.first;
+ Color seedColor = defaultSettings.second;
+
+ // we only use the first color anyway, so we can pass only the single color we have
+ systemColor = new WallpaperColors(
+ /*primaryColor*/ seedColor,
+ /*secondaryColor*/ seedColor,
+ /*tertiaryColor*/ seedColor
+ );
+ } else {
+ systemColor = mWallpaperManager.getWallpaperColors(
+ getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
+ }
+
// Upon boot, make sure we have the most up to date colors
Runnable updateColors = () -> {
- WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
- getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
- Runnable applyColors = () -> {
- if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
- mCurrentColors.put(mUserTracker.getUserId(), systemColor);
- reevaluateSystemTheme(false /* forceReload */);
- };
- if (mDeviceProvisionedController.isCurrentUserSetup()) {
- mMainExecutor.execute(applyColors);
- } else {
- applyColors.run();
- }
+ if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
+ mCurrentColors.put(mUserTracker.getUserId(), systemColor);
+ reevaluateSystemTheme(false /* forceReload */);
};
// Whenever we're going directly to setup wizard, we need to process colors synchronously,
// otherwise we'll see some jank when the activity is recreated.
if (!mDeviceProvisionedController.isCurrentUserSetup()) {
- updateColors.run();
+ mMainExecutor.execute(updateColors);
} else {
mBgExecutor.execute(updateColors);
}
+
mWallpaperManager.addOnColorsChangedListener(mOnColorsChangedListener, null,
UserHandle.USER_ALL);
@@ -604,7 +622,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
@VisibleForTesting
protected boolean isPrivateProfile(UserHandle userHandle) {
- Context usercontext = mContext.createContextAsUser(userHandle,0);
+ Context usercontext = mContext.createContextAsUser(userHandle, 0);
return usercontext.getSystemService(UserManager.class).isPrivateProfile();
}
@@ -720,6 +738,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
return true;
}
+ @SuppressWarnings("StringCaseLocaleUsage") // Package name is not localized
private void updateThemeOverlays() {
final int currentUser = mUserTracker.getUserId();
final String overlayPackageJson = mSecureSettings.getStringForUser(
@@ -746,7 +765,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
try {
- String colorString = systemPalette.getPackageName().toLowerCase();
+ String colorString = systemPalette.getPackageName().toLowerCase();
if (!colorString.startsWith("#")) {
colorString = "#" + colorString;
}
@@ -856,6 +875,75 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
return style;
}
+ protected Pair<Integer, String> getHardwareColorSetting() {
+ String deviceColorProperty = "ro.boot.hardware.color";
+
+ String[] themeData = mResources.getStringArray(
+ com.android.internal.R.array.theming_defaults);
+
+ // Color can be hex (`#FF0000`) or `home_wallpaper`
+ Map<String, Pair<Integer, String>> themeMap = new HashMap<>();
+
+ // extract all theme settings
+ for (String themeEntry : themeData) {
+ String[] themeComponents = themeEntry.split("\\|");
+ if (themeComponents.length != 3) continue;
+ themeMap.put(themeComponents[0],
+ new Pair<>(Style.valueOf(themeComponents[1]), themeComponents[2]));
+ }
+
+ Pair<Integer, String> fallbackTheme = themeMap.get("*");
+ if (fallbackTheme == null) {
+ Log.d(TAG, "Theming wildcard not found. Fallback to TONAL_SPOT|" + COLOR_SOURCE_HOME);
+ fallbackTheme = new Pair<>(Style.TONAL_SPOT, COLOR_SOURCE_HOME);
+ }
+
+ String deviceColorPropertyValue = mSystemPropertiesHelper.get(deviceColorProperty);
+ Pair<Integer, String> selectedTheme = themeMap.get(deviceColorPropertyValue);
+ if (selectedTheme == null) {
+ Log.d(TAG, "Sysprop `" + deviceColorProperty + "` of value '" + deviceColorPropertyValue
+ + "' not found in theming_defaults: " + Arrays.toString(themeData));
+ selectedTheme = fallbackTheme;
+ }
+
+ return selectedTheme;
+ }
+
+ @VisibleForTesting
+ protected Pair<Integer, Color> getThemeSettingsDefaults() {
+
+ Pair<Integer, String> selectedTheme = getHardwareColorSetting();
+
+ // Last fallback color
+ Color defaultSeedColor = Color.valueOf(GOOGLE_BLUE);
+
+ // defaultColor will come from wallpaper or be parsed from a string
+ boolean isWallpaper = selectedTheme.second.equals(COLOR_SOURCE_HOME);
+
+ if (isWallpaper) {
+ WallpaperColors wallpaperColors = mWallpaperManager.getWallpaperColors(
+ getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
+
+ if (wallpaperColors != null) {
+ defaultSeedColor = wallpaperColors.getPrimaryColor();
+ }
+
+ Log.d(TAG, "Default seed color read from home wallpaper: " + Integer.toHexString(
+ defaultSeedColor.toArgb()));
+ } else {
+ try {
+ defaultSeedColor = Color.valueOf(Color.parseColor(selectedTheme.second));
+ Log.d(TAG, "Default seed color read from resource: " + Integer.toHexString(
+ defaultSeedColor.toArgb()));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Error parsing color: " + selectedTheme.second, e);
+ // defaultSeedColor remains unchanged in this case
+ }
+ }
+
+ return new Pair<>(selectedTheme.first, defaultSeedColor);
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("mSystemColors=" + mCurrentColors);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 6d6e1fb6bfb3..183990c01072 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -18,6 +18,7 @@ package com.android.server.audio;
import static android.media.audio.Flags.scoManagedByAudio;
import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
+import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_SCO;
@@ -290,8 +291,8 @@ public class AudioDeviceBroker {
}
@GuardedBy("mDeviceStateLock")
- /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
- mBtHelper.onSetBtScoActiveDevice(btDevice);
+ /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) {
+ mBtHelper.onSetBtScoActiveDevice(btDevice, deviceSwitch);
}
/*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
@@ -941,6 +942,7 @@ public class AudioDeviceBroker {
final @NonNull String mEventSource;
final int mAudioSystemDevice;
final int mMusicDevice;
+ final boolean mIsDeviceSwitch;
BtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device, int state,
int audioDevice, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) {
@@ -953,6 +955,8 @@ public class AudioDeviceBroker {
mEventSource = d.mEventSource;
mAudioSystemDevice = audioDevice;
mMusicDevice = AudioSystem.DEVICE_NONE;
+ mIsDeviceSwitch = optimizeBtDeviceSwitch()
+ && d.mNewDevice != null && d.mPreviousDevice != null;
}
// constructor used by AudioDeviceBroker to search similar message
@@ -966,6 +970,7 @@ public class AudioDeviceBroker {
mSupprNoisy = false;
mVolume = -1;
mIsLeOutput = false;
+ mIsDeviceSwitch = false;
}
// constructor used by AudioDeviceInventory when config change failed
@@ -980,6 +985,7 @@ public class AudioDeviceBroker {
mSupprNoisy = false;
mVolume = -1;
mIsLeOutput = false;
+ mIsDeviceSwitch = false;
}
BtDeviceInfo(@NonNull BtDeviceInfo src, int state) {
@@ -992,6 +998,7 @@ public class AudioDeviceBroker {
mEventSource = src.mEventSource;
mAudioSystemDevice = src.mAudioSystemDevice;
mMusicDevice = src.mMusicDevice;
+ mIsDeviceSwitch = false;
}
// redefine equality op so we can match messages intended for this device
@@ -1026,7 +1033,8 @@ public class AudioDeviceBroker {
+ " isLeOutput=" + mIsLeOutput
+ " eventSource=" + mEventSource
+ " audioSystemDevice=" + mAudioSystemDevice
- + " musicDevice=" + mMusicDevice;
+ + " musicDevice=" + mMusicDevice
+ + " isDeviceSwitch=" + mIsDeviceSwitch;
}
}
@@ -1680,10 +1688,11 @@ public class AudioDeviceBroker {
}
/*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
- boolean connect, @Nullable BluetoothDevice btDevice) {
+ boolean connect, @Nullable BluetoothDevice btDevice,
+ boolean deviceSwitch) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.handleDeviceConnection(
- attributes, connect, false /*for test*/, btDevice);
+ attributes, connect, false /*for test*/, btDevice, deviceSwitch);
}
}
@@ -1930,10 +1939,12 @@ public class AudioDeviceBroker {
|| btInfo.mIsLeOutput)
? mAudioService.getBluetoothContextualVolumeStream()
: AudioSystem.STREAM_DEFAULT);
- if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
+ if ((btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.HEARING_AID
|| (mScoManagedByAudio
- && btInfo.mProfile == BluetoothProfile.HEADSET)) {
+ && btInfo.mProfile == BluetoothProfile.HEADSET))
+ && (btInfo.mState == BluetoothProfile.STATE_CONNECTED
+ || !btInfo.mIsDeviceSwitch)) {
onUpdateCommunicationRouteClient(
bluetoothScoRequestOwnerAttributionSource(),
"setBluetoothActiveDevice");
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ef10793fd955..ae91934e7498 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -799,7 +799,7 @@ public class AudioDeviceInventory {
di.mDeviceAddress,
di.mDeviceName),
AudioSystem.DEVICE_STATE_AVAILABLE,
- di.mDeviceCodecFormat);
+ di.mDeviceCodecFormat, false /*deviceSwitch*/);
if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) {
failedReconnectionDeviceList.add(di);
}
@@ -811,7 +811,7 @@ public class AudioDeviceInventory {
EventLogger.Event.ALOGE, TAG);
mConnectedDevices.remove(di.getKey(), di);
if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
- mDeviceBroker.onSetBtScoActiveDevice(null);
+ mDeviceBroker.onSetBtScoActiveDevice(null, false /*deviceSwitch*/);
}
}
}
@@ -851,7 +851,8 @@ public class AudioDeviceInventory {
Log.d(TAG, "onSetBtActiveDevice"
+ " btDevice=" + btInfo.mDevice
+ " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile)
- + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState));
+ + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState)
+ + " isDeviceSwitch=" + btInfo.mIsDeviceSwitch);
}
String address = btInfo.mDevice.getAddress();
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -897,7 +898,8 @@ public class AudioDeviceInventory {
break;
case BluetoothProfile.A2DP:
if (switchToUnavailable) {
- makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+ makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat,
+ btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
// device is not already connected
if (btInfo.mVolume != -1) {
@@ -911,7 +913,7 @@ public class AudioDeviceInventory {
break;
case BluetoothProfile.HEARING_AID:
if (switchToUnavailable) {
- makeHearingAidDeviceUnavailable(address);
+ makeHearingAidDeviceUnavailable(address, btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
streamType, "onSetBtActiveDevice");
@@ -921,7 +923,8 @@ public class AudioDeviceInventory {
case BluetoothProfile.LE_AUDIO_BROADCAST:
if (switchToUnavailable) {
makeLeAudioDeviceUnavailableNow(address,
- btInfo.mAudioSystemDevice, di.mDeviceCodecFormat);
+ btInfo.mAudioSystemDevice, di.mDeviceCodecFormat,
+ btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(
btInfo, streamType, codec, "onSetBtActiveDevice");
@@ -930,9 +933,10 @@ public class AudioDeviceInventory {
case BluetoothProfile.HEADSET:
if (mDeviceBroker.isScoManagedByAudio()) {
if (switchToUnavailable) {
- mDeviceBroker.onSetBtScoActiveDevice(null);
+ mDeviceBroker.onSetBtScoActiveDevice(null, btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
- mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice);
+ mDeviceBroker.onSetBtScoActiveDevice(
+ btInfo.mDevice, false /*deviceSwitch*/);
}
}
break;
@@ -1053,19 +1057,19 @@ public class AudioDeviceInventory {
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
synchronized (mDevicesLock) {
- makeA2dpDeviceUnavailableNow(address, a2dpCodec);
+ makeA2dpDeviceUnavailableNow(address, a2dpCodec, false /*deviceSwitch*/);
}
}
/*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device, int codec) {
synchronized (mDevicesLock) {
- makeLeAudioDeviceUnavailableNow(address, device, codec);
+ makeLeAudioDeviceUnavailableNow(address, device, codec, false /*deviceSwitch*/);
}
}
/*package*/ void onMakeHearingAidDeviceUnavailableNow(String address) {
synchronized (mDevicesLock) {
- makeHearingAidDeviceUnavailable(address);
+ makeHearingAidDeviceUnavailable(address, false /*deviceSwitch*/);
}
}
@@ -1180,7 +1184,8 @@ public class AudioDeviceInventory {
}
if (!handleDeviceConnection(wdcs.mAttributes,
- wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
+ wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest,
+ null, false /*deviceSwitch*/)) {
// change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record();
@@ -1788,14 +1793,15 @@ public class AudioDeviceInventory {
*/
/*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
boolean connect, boolean isForTesting,
- @Nullable BluetoothDevice btDevice) {
+ @Nullable BluetoothDevice btDevice,
+ boolean deviceSwitch) {
int device = attributes.getInternalType();
String address = attributes.getAddress();
String deviceName = attributes.getName();
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ Integer.toHexString(device) + " address:" + address
- + " name:" + deviceName + ")");
+ + " name:" + deviceName + ", deviceSwitch: " + deviceSwitch + ")");
}
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection")
.set(MediaMetrics.Property.ADDRESS, address)
@@ -1829,7 +1835,8 @@ public class AudioDeviceInventory {
res = AudioSystem.AUDIO_STATUS_OK;
} else {
res = mAudioSystem.setDeviceConnectionState(attributes,
- AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT,
+ false /*deviceSwitch*/);
}
if (res != AudioSystem.AUDIO_STATUS_OK) {
final String reason = "not connecting device 0x" + Integer.toHexString(device)
@@ -1856,7 +1863,8 @@ public class AudioDeviceInventory {
status = true;
} else if (!connect && isConnected) {
mAudioSystem.setDeviceConnectionState(attributes,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT,
+ deviceSwitch);
// always remove even if disconnection failed
mConnectedDevices.remove(deviceKey);
mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
@@ -2030,7 +2038,7 @@ public class AudioDeviceInventory {
}
}
if (disconnect) {
- mDeviceBroker.onSetBtScoActiveDevice(null);
+ mDeviceBroker.onSetBtScoActiveDevice(null, false /*deviceSwitch*/);
}
}
@@ -2068,7 +2076,8 @@ public class AudioDeviceInventory {
|| info.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST)
&& info.mIsLeOutput)
|| info.mProfile == BluetoothProfile.HEARING_AID
- || info.mProfile == BluetoothProfile.A2DP)) {
+ || info.mProfile == BluetoothProfile.A2DP)
+ && !info.mIsDeviceSwitch) {
@AudioService.ConnectionState int asState =
(info.mState == BluetoothProfile.STATE_CONNECTED)
? AudioService.CONNECTION_STATE_CONNECTED
@@ -2124,7 +2133,7 @@ public class AudioDeviceInventory {
AudioDeviceAttributes ada = new AudioDeviceAttributes(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_AVAILABLE, codec);
+ AudioSystem.DEVICE_STATE_AVAILABLE, codec, false);
// TODO: log in MediaMetrics once distinction between connection failure and
// double connection is made.
@@ -2362,7 +2371,7 @@ public class AudioDeviceInventory {
}
@GuardedBy("mDevicesLock")
- private void makeA2dpDeviceUnavailableNow(String address, int codec) {
+ private void makeA2dpDeviceUnavailableNow(String address, int codec, boolean deviceSwitch) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
.set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
.set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow");
@@ -2393,7 +2402,7 @@ public class AudioDeviceInventory {
AudioDeviceAttributes ada = new AudioDeviceAttributes(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, codec);
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, codec, deviceSwitch);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
@@ -2404,7 +2413,8 @@ public class AudioDeviceInventory {
} else {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable")).printSlog(EventLogger.Event.ALOGI, TAG));
+ + " made unavailable, deviceSwitch" + deviceSwitch))
+ .printSlog(EventLogger.Event.ALOGI, TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -2440,7 +2450,7 @@ public class AudioDeviceInventory {
final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make available A2DP source device addr="
@@ -2465,7 +2475,7 @@ public class AudioDeviceInventory {
AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false);
// always remove regardless of the result
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
@@ -2485,7 +2495,7 @@ public class AudioDeviceInventory {
DEVICE_OUT_HEARING_AID, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false);
if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueueAndSlog(
"APM failed to make available HearingAid addr=" + address
@@ -2515,12 +2525,12 @@ public class AudioDeviceInventory {
}
@GuardedBy("mDevicesLock")
- private void makeHearingAidDeviceUnavailable(String address) {
+ private void makeHearingAidDeviceUnavailable(String address, boolean deviceSwitch) {
AudioDeviceAttributes ada = new AudioDeviceAttributes(
DEVICE_OUT_HEARING_AID, address);
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, deviceSwitch);
// always remove regardless of return code
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
@@ -2622,7 +2632,7 @@ public class AudioDeviceInventory {
AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_AVAILABLE, codec);
+ AudioSystem.DEVICE_STATE_AVAILABLE, codec, false /*deviceSwitch*/);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueueAndSlog(
"APM failed to make available LE Audio device addr=" + address
@@ -2669,13 +2679,13 @@ public class AudioDeviceInventory {
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceUnavailableNow(String address, int device,
- @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, boolean deviceSwitch) {
AudioDeviceAttributes ada = null;
if (device != AudioSystem.DEVICE_NONE) {
ada = new AudioDeviceAttributes(device, address);
final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
- codec);
+ codec, deviceSwitch);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
@@ -2685,7 +2695,8 @@ public class AudioDeviceInventory {
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable").printSlog(EventLogger.Event.ALOGI, TAG));
+ + " made unavailable, deviceSwitch" + deviceSwitch)
+ .printSlog(EventLogger.Event.ALOGI, TAG));
}
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 86871ea45d13..057d1274d47d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,7 @@ import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.deferWearPermissionUpdates;
import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
+import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
import static com.android.media.audio.Flags.replaceStreamBtSco;
import static com.android.media.audio.Flags.ringMyCar;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
@@ -4990,6 +4991,8 @@ public class AudioService extends IAudioService.Stub
+ cacheGetStreamMinMaxVolume());
pw.println("\tandroid.media.audio.Flags.cacheGetStreamVolume:"
+ cacheGetStreamVolume());
+ pw.println("\tcom.android.media.audio.optimizeBtDeviceSwitch:"
+ + optimizeBtDeviceSwitch());
}
private void dumpAudioMode(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index e86c34cab88a..a6267c156fb3 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -367,9 +367,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
* @return
*/
public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
- int codecFormat) {
+ int codecFormat, boolean deviceSwitch) {
invalidateRoutingCache();
- return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat);
+ return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat, deviceSwitch);
}
/**
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 922116999bc7..844e3524384d 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -26,6 +26,8 @@ import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH;
+import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothA2dp;
@@ -393,8 +395,11 @@ public class BtHelper {
+ "received with null profile proxy for device: "
+ btDevice)).printLog(TAG));
return;
+
}
- onSetBtScoActiveDevice(btDevice);
+ boolean deviceSwitch = optimizeBtDeviceSwitch()
+ && btDevice != null && mBluetoothHeadsetDevice != null;
+ onSetBtScoActiveDevice(btDevice, deviceSwitch);
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
onScoAudioStateChanged(btState);
@@ -814,7 +819,7 @@ public class BtHelper {
if (device == null) {
continue;
}
- onSetBtScoActiveDevice(device);
+ onSetBtScoActiveDevice(device, false /*deviceSwitch*/);
}
} else {
Log.e(TAG, "onHeadsetProfileConnected: Null BluetoothAdapter");
@@ -907,7 +912,8 @@ public class BtHelper {
}
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
+ private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive,
+ boolean deviceSwitch) {
if (btDevice == null) {
return true;
}
@@ -919,12 +925,12 @@ public class BtHelper {
if (isActive) {
audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
result = mDeviceBroker.handleDeviceConnection(
- audioDevice, true /*connect*/, btDevice);
+ audioDevice, true /*connect*/, btDevice, false /*deviceSwitch*/);
} else {
AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice);
if (ada != null) {
result = mDeviceBroker.handleDeviceConnection(
- ada, false /*connect*/, btDevice);
+ ada, false /*connect*/, btDevice, deviceSwitch);
} else {
// Disconnect all possible audio device types if the disconnected device type is
// unknown
@@ -935,7 +941,8 @@ public class BtHelper {
};
for (int outDeviceType : outDeviceTypes) {
result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
- outDeviceType, address, name), false /*connect*/, btDevice);
+ outDeviceType, address, name), false /*connect*/, btDevice,
+ deviceSwitch);
}
}
}
@@ -944,7 +951,7 @@ public class BtHelper {
// handleDeviceConnection() && result to make sure the method get executed
result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
inDevice, address, name),
- isActive, btDevice) && result;
+ isActive, btDevice, deviceSwitch) && result;
if (result) {
if (isActive) {
mResolvedScoAudioDevices.put(btDevice, audioDevice);
@@ -961,18 +968,18 @@ public class BtHelper {
}
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
+ /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) {
Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
- + " -> " + getAnonymizedAddress(btDevice));
+ + " -> " + getAnonymizedAddress(btDevice) + ", deviceSwitch: " + deviceSwitch);
final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
if (Objects.equals(btDevice, previousActiveDevice)) {
return;
}
- if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+ if (!handleBtScoActiveDeviceChange(previousActiveDevice, false, deviceSwitch)) {
Log.w(TAG, "onSetBtScoActiveDevice() failed to remove previous device "
+ getAnonymizedAddress(previousActiveDevice));
}
- if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+ if (!handleBtScoActiveDeviceChange(btDevice, true, false /*deviceSwitch*/)) {
Log.e(TAG, "onSetBtScoActiveDevice() failed to add new device "
+ getAnonymizedAddress(btDevice));
// set mBluetoothHeadsetDevice to null when failing to add new device
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 70143f1c1a98..acdc0e0cf891 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -456,9 +456,8 @@ flag {
flag {
name: "enable_display_content_mode_management"
namespace: "lse_desktop_experience"
- description: "Enable switching the content mode of connected displays between mirroring and extened. Also change the default content mode to extended mode."
+ description: "Enable switching the content mode of connected displays between mirroring and extended. Also change the default content mode to extended mode."
bug: "378385869"
- is_fixed_read_only: true
}
flag {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 334e7b5240ce..70044bd813ce 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1332,8 +1332,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// Do not reset the default (current) IME when it is a 3rd-party IME
String selectedMethodId = bindingController.getSelectedMethodId();
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (selectedMethodId != null && settings.getMethodMap().get(selectedMethodId) != null
- && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
+ final InputMethodInfo selectedImi = settings.getMethodMap().get(selectedMethodId);
+ if (selectedImi != null && !selectedImi.isSystem()) {
return;
}
final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index a96c477c78d2..f731b50d81b4 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -17,6 +17,8 @@
package com.android.server.supervision;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_USERS;
+import static android.Manifest.permission.QUERY_USERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.Preconditions.checkCallAuthorization;
@@ -79,6 +81,25 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
/**
+ * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
+ * launch the activity to verify supervision credentials.
+ *
+ * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
+ * method is called, the launched activity still need to perform validity checks as the
+ * supervision state can change when it's launched. A null intent is returned if supervision is
+ * disabled at the time of this method call.
+ *
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
+ * of the supervision credentials.
+ */
+ @Override
+ @Nullable
+ public Intent createConfirmSupervisionCredentialsIntent() {
+ // TODO(b/392961554): Implement createAuthenticationIntent API
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Returns whether supervision is enabled for the given user.
*
* <p>Supervision is automatically enabled when the supervision app becomes the profile owner or
@@ -86,6 +107,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
*/
@Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
+ enforceAnyPermission(QUERY_USERS, MANAGE_USERS);
if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
enforcePermission(INTERACT_ACROSS_USERS);
}
@@ -96,6 +118,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
@Override
public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+ // TODO(b/395630828): Ensure that this method can only be called by the system.
if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
enforcePermission(INTERACT_ACROSS_USERS);
}
@@ -181,8 +204,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
* Ensures that supervision is enabled when the supervision app is the profile owner.
*
* <p>The state syncing with the DevicePolicyManager can only enable supervision and never
- * disable. Supervision can only be disabled explicitly via calls to the
- * {@link #setSupervisionEnabledForUser} method.
+ * disable. Supervision can only be disabled explicitly via calls to the {@link
+ * #setSupervisionEnabledForUser} method.
*/
private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal();
@@ -221,6 +244,17 @@ public class SupervisionService extends ISupervisionManager.Stub {
mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED);
}
+ /** Enforces that the caller has at least one of the given permission. */
+ private void enforceAnyPermission(String... permissions) {
+ boolean authorized = false;
+ for (String permission : permissions) {
+ if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ authorized = true;
+ }
+ }
+ checkCallAuthorization(authorized);
+ }
+
/** Provides local services in a lazy manner. */
static class Injector {
private final Context mContext;
@@ -280,7 +314,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
@VisibleForTesting
- @SuppressLint("MissingPermission") // not needed for a system service
+ @SuppressLint("MissingPermission")
void registerProfileOwnerListener() {
IntentFilter poIntentFilter = new IntentFilter();
poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 5d64cb638702..2d3f7231cc5c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -34,12 +34,12 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.res.Configuration;
import android.graphics.Insets;
+import android.os.Build;
import android.os.RemoteException;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.server.wm.WindowManagerStateHelper;
@@ -86,25 +86,36 @@ public class InputMethodServiceTest {
"android:id/input_method_nav_back";
private static final String INPUT_METHOD_NAV_IME_SWITCHER_ID =
"android:id/input_method_nav_ime_switcher";
- private static final long TIMEOUT_IN_SECONDS = 3;
- private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
- "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1";
- private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
- "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
+
+ /** Timeout until the uiObject should be found. */
+ private static final long TIMEOUT_MS = 5000L * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /** Timeout until the event is expected. */
+ private static final long EXPECT_TIMEOUT_MS = 3000L * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /** Timeout during which the event is not expected. */
+ private static final long NOT_EXCEPT_TIMEOUT_MS = 2000L * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /** Command to set showing the IME when a hardware keyboard is connected. */
+ private static final String SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
+ "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD;
+ /** Command to get verbose ImeTracker logging state. */
+ private static final String GET_VERBOSE_IME_TRACKER_LOGGING_CMD =
+ "getprop persist.debug.imetracker";
+ /** Command to set verbose ImeTracker logging state. */
+ private static final String SET_VERBOSE_IME_TRACKER_LOGGING_CMD =
+ "setprop persist.debug.imetracker";
/** The ids of the subtypes of SimpleIme. */
private static final int[] SUBTYPE_IDS = new int[]{1, 2};
- private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+ private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();
private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
@Rule
- public final CheckFlagsRule mCheckFlagsRule = new CheckFlagsRule(mFlagsValueProvider);
-
- @Rule
public final TestName mName = new TestName();
private Instrumentation mInstrumentation;
@@ -114,7 +125,8 @@ public class InputMethodServiceTest {
private String mInputMethodId;
private TestActivity mActivity;
private InputMethodServiceWrapper mInputMethodService;
- private boolean mShowImeWithHardKeyboardEnabled;
+ private boolean mOriginalVerboseImeTrackerLoggingEnabled;
+ private boolean mOriginalShowImeWithHardKeyboardEnabled;
@Before
public void setUp() throws Exception {
@@ -123,9 +135,12 @@ public class InputMethodServiceTest {
mImm = mInstrumentation.getContext().getSystemService(InputMethodManager.class);
mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
mInputMethodId = getInputMethodId();
+ mOriginalVerboseImeTrackerLoggingEnabled = getVerboseImeTrackerLogging();
+ if (!mOriginalVerboseImeTrackerLoggingEnabled) {
+ setVerboseImeTrackerLogging(true);
+ }
prepareIme();
prepareActivity();
- mInstrumentation.waitForIdleSync();
mUiDevice.freezeRotation();
mUiDevice.setOrientationNatural();
// Waits for input binding ready.
@@ -148,17 +163,18 @@ public class InputMethodServiceTest {
.that(mInputMethodService.getCurrentInputViewStarted()).isFalse();
});
// Save the original value of show_ime_with_hard_keyboard from Settings.
- mShowImeWithHardKeyboardEnabled =
+ mOriginalShowImeWithHardKeyboardEnabled =
mInputMethodService.getShouldShowImeWithHardKeyboardForTesting();
}
@After
public void tearDown() throws Exception {
mUiDevice.unfreezeRotation();
+ if (!mOriginalVerboseImeTrackerLoggingEnabled) {
+ setVerboseImeTrackerLogging(false);
+ }
// Change back the original value of show_ime_with_hard_keyboard in Settings.
- executeShellCommand(mShowImeWithHardKeyboardEnabled
- ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
- : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
+ setShowImeWithHardKeyboard(mOriginalShowImeWithHardKeyboardEnabled);
executeShellCommand("ime disable " + mInputMethodId);
}
@@ -170,7 +186,7 @@ public class InputMethodServiceTest {
public void testShowHideKeyboard_byUserAction() {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
// Performs click on EditText to bring up the IME.
Log.i(TAG, "Click on EditText");
@@ -201,14 +217,12 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowHideKeyboard_byInputMethodManager() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- // Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
@@ -219,14 +233,12 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowHideKeyboard_byInsetsController() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- // Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.hideImeWithWindowInsetsController(),
EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
@@ -234,53 +246,18 @@ public class InputMethodServiceTest {
/**
* This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
- *
- * <p>With the refactor in b/298172246, all calls to IMMS#{show,hide}MySoftInputLocked
- * will be just apply the requested visibility (by using the callback). Therefore, we will
- * lose flags like HIDE_IMPLICIT_ONLY.
*/
@Test
public void testShowHideSelf() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // IME request to show itself without any flags, expect shown.
- Log.i(TAG, "Call IMS#requestShowSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(0 /* flags */),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown).
- Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.requestHideSelf(
- InputMethodManager.HIDE_IMPLICIT_ONLY),
- EVENT_HIDE, false /* eventExpected */, true /* shown */,
- "IME is still shown after HIDE_IMPLICIT_ONLY");
- }
-
- // IME request to hide itself without any flags, expect hidden.
- Log.i(TAG, "Call IMS#requestHideSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(0 /* flags */),
EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
-
- if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // IME request to show itself with flag SHOW_IMPLICIT, expect shown.
- Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
- EVENT_SHOW, true /* eventExpected */, true /* shown */,
- "IME is shown with SHOW_IMPLICIT");
-
- // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden.
- Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.requestHideSelf(
- InputMethodManager.HIDE_IMPLICIT_ONLY),
- EVENT_HIDE, true /* eventExpected */, false /* shown */,
- "IME is not shown after HIDE_IMPLICIT_ONLY");
- }
}
/**
@@ -289,28 +266,25 @@ public class InputMethodServiceTest {
*/
@Test
public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
try {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should show with visible hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show with visible hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
config.keyboard = Configuration.KEYBOARD_NOKEYS;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should show without hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show without hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- eventually(() ->
- assertWithMessage("InputView should show with hidden hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show with hidden hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -323,28 +297,25 @@ public class InputMethodServiceTest {
*/
@Test
public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
try {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should not show with visible hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isFalse());
+ assertWithMessage("InputView should not show with visible hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isFalse();
config.keyboard = Configuration.KEYBOARD_NOKEYS;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should show without hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show without hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- eventually(() ->
- assertWithMessage("InputView should show with hidden hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show with hidden hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -357,7 +328,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInput_disableShowImeWithHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -386,49 +357,17 @@ public class InputMethodServiceTest {
}
/**
- * This checks that an explicit show request results in the IME being shown.
- */
- @Test
- public void testShowSoftInputExplicitly() {
- setShowImeWithHardKeyboard(true /* enabled */);
-
- // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the
- // IME should be shown.
- verifyInputViewStatusOnMainSync(
- () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- }
-
- /**
- * This checks that an implicit show request results in the IME being shown.
- */
- @Test
- public void testShowSoftInputImplicitly() {
- setShowImeWithHardKeyboard(true /* enabled */);
-
- // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT,
- // the IME should be shown.
- verifyInputViewStatusOnMainSync(() -> assertThat(
- mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- }
-
- /**
* This checks that an explicit show request when the IME is not previously shown,
* and it should be shown in fullscreen mode, results in the IME being shown.
*/
@Test
public void testShowSoftInputExplicitly_fullScreenMode() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
// Set orientation landscape to enable fullscreen mode.
setOrientation(2);
- eventually(() -> assertWithMessage("No longer in natural orientation")
- .that(mUiDevice.isNaturalOrientation()).isFalse());
- // Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
.that(TestActivity.getInstance()).isNotEqualTo(mActivity));
- // Get the new TestActivity.
mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
@@ -442,34 +381,40 @@ public class InputMethodServiceTest {
/**
* This checks that an implicit show request when the IME is not previously shown,
- * and it should be shown in fullscreen mode, results in the IME not being shown.
+ * and it should be shown in fullscreen mode behaves like an explicit show request, resulting
+ * in the IME being shown. This is due to the refactor in b/298172246, causing us to lose flag
+ * information like {@link InputMethodManager#SHOW_IMPLICIT}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * SHOW_IMPLICIT.
+ * <p>Previously, an implicit show request when the IME is not previously shown,
+ * and it should be shown in fullscreen mode, would result in the IME not being shown.
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_fullScreenMode() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
// Set orientation landscape to enable fullscreen mode.
setOrientation(2);
- eventually(() -> assertWithMessage("No longer in natural orientation")
- .that(mUiDevice.isNaturalOrientation()).isFalse());
- // Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
.that(TestActivity.getInstance()).isNotEqualTo(mActivity));
- // Get the new TestActivity.
mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
.that(mImm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
- verifyInputViewStatusOnMainSync(() -> assertThat(
- mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ } else {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
+ }
}
/**
@@ -478,7 +423,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInputExplicitly_withHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -497,17 +442,17 @@ public class InputMethodServiceTest {
}
/**
- * This checks that an implicit show request when a hardware keyboard is connected,
- * results in the IME not being shown.
+ * This checks that an implicit show request when a hardware keyboard is connected behaves
+ * like an explicit show request, resulting in the IME being shown. This is due to the
+ * refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_IMPLICIT}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * SHOW_IMPLICIT.
+ * <p>Previously, an implicit show request when a hardware keyboard is connected would
+ * result in the IME not being shown.
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_withHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -516,10 +461,20 @@ public class InputMethodServiceTest {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- verifyInputViewStatusOnMainSync(() ->assertThat(
- mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT))
- .isTrue(),
- EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ } else {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, false /* eventExpected */, false /* shown */,
+ "IME is not shown");
+ }
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -532,7 +487,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInputExplicitly_thenConfigurationChanged() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -565,17 +520,17 @@ public class InputMethodServiceTest {
/**
* This checks that an implicit show request followed by connecting a hardware keyboard
- * and a configuration change, does not trigger IMS#onFinishInputView,
- * but results in the IME being hidden.
+ * and a configuration change behaves like an explicit show request, resulting in the IME
+ * still being shown. This is due to the refactor in b/298172246, causing us to lose flag
+ * information like {@link InputMethodManager#SHOW_IMPLICIT}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * SHOW_IMPLICIT.
+ * <p>Previously, an implicit show request followed by connecting a hardware keyboard
+ * and a configuration change, would not trigger IMS#onFinishInputView, but resulted in the
+ * IME being hidden.
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_thenConfigurationChanged() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -596,16 +551,23 @@ public class InputMethodServiceTest {
// Simulate a fake configuration change to avoid the recreation of TestActivity.
config.orientation = Configuration.ORIENTATION_LANDSCAPE;
- // Normally, IMS#onFinishInputView will be called when finishing the input view by
- // the user. But if IMS#hideWindow is called when receiving a new configuration change,
- // we don't expect that it's user-driven to finish the lifecycle of input view with
- // IMS#onFinishInputView, because the input view will be re-initialized according
- // to the last #mShowInputRequested state. So in this case we treat the input view as
- // still alive.
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG, true /* eventExpected */, true /* inputViewStarted */,
- false /* shown */, "IME is not shown after a configuration change");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.onConfigurationChanged(config),
+ EVENT_CONFIG, true /* eventExpected */, true /* shown */,
+ "IME is still shown after a configuration change");
+ } else {
+ // Normally, IMS#onFinishInputView will be called when finishing the input view by
+ // the user. But if IMS#hideWindow is called when receiving a new configuration
+ // change, we don't expect that it's user-driven to finish the lifecycle of input
+ // view with IMS#onFinishInputView, because the input view will be re-initialized
+ // according to the last #mShowInputRequested state. So in this case we treat the
+ // input view as still alive.
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.onConfigurationChanged(config),
+ EVENT_CONFIG, true /* eventExpected */, true /* inputViewStarted */,
+ false /* shown */, "IME is not shown after a configuration change");
+ }
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -619,7 +581,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInputExplicitly_thenShowSoftInputImplicitly_withHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -628,12 +590,10 @@ public class InputMethodServiceTest {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- // Explicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- // Implicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
@@ -654,17 +614,18 @@ public class InputMethodServiceTest {
/**
* This checks that a forced show request directly followed by an explicit show request,
- * and then a hide not always request, still results in the IME being shown
- * (i.e. the explicit show request retains the forced state).
+ * and then a not always hide request behaves like a normal hide request, resulting in the
+ * IME being hidden (i.e. the explicit show request does not retain the forced state). This is
+ * due to the refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_FORCED}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * HIDE_NOT_ALWAYS.
+ * <p>Previously, a forced show request directly followed by an explicit show request,
+ * and then a not always hide request, would result in the IME still being shown
+ * (i.e. the explicit show request would retain the forced state).
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
@@ -674,11 +635,123 @@ public class InputMethodServiceTest {
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_SHOW, false /* eventExpected */, true /* shown */, "IME is still shown");
- verifyInputViewStatusOnMainSync(() -> assertThat(
- mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
- .isTrue(),
- EVENT_HIDE, false /* eventExpected */, true /* shown */,
- "IME is still shown after HIDE_NOT_ALWAYS");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(() -> assertThat(mActivity
+ .hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
+ .isTrue(),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_NOT_ALWAYS");
+ } else {
+ verifyInputViewStatusOnMainSync(() -> assertThat(mActivity
+ .hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
+ .isTrue(),
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_NOT_ALWAYS");
+ }
+ }
+
+ /**
+ * This checks that an explicit show request followed by an implicit only hide request
+ * behaves like a normal hide request, resulting in the IME being hidden. This is due to
+ * the refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_IMPLICIT} and {@link InputMethodManager#HIDE_IMPLICIT_ONLY}.
+ *
+ * <p>Previously, an explicit show request followed by an implicit only hide request
+ * would result in the IME still being shown.
+ */
+ @Test
+ public void testShowSoftInputExplicitly_thenHideSoftInputImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithInputMethodManager(0 /* flags */),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.hideImeWithInputMethodManager(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
+ } else {
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.hideImeWithInputMethodManager(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_IMPLICIT_ONLY");
+ }
+ }
+
+ /**
+ * This checks that an implicit show request followed by an implicit only hide request
+ * results in the IME being hidden.
+ */
+ @Test
+ public void testShowSoftInputImplicitly_thenHideSoftInputImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */,
+ "IME is shown with SHOW_IMPLICIT");
+
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.hideImeWithInputMethodManager(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
+ }
+
+ /**
+ * This checks that an explicit show self request followed by an implicit only hide self request
+ * behaves like a normal hide self request, resulting in the IME being hidden. This is due to
+ * the refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_IMPLICIT} and {@link InputMethodManager#HIDE_IMPLICIT_ONLY}.
+ *
+ * <p>Previously, an explicit show self request followed by an implicit only hide self request
+ * would result in the IME still being shown.
+ */
+ @Test
+ public void testShowSelfExplicitly_thenHideSelfImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(0 /* flags */),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
+ } else {
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_IMPLICIT_ONLY");
+ }
+ }
+
+ /**
+ * This checks that an implicit show self request followed by an implicit only hide self request
+ * results in the IME being hidden.
+ */
+ @Test
+ public void testShowSelfImplicitly_thenHideSelfImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */,
+ "IME is shown with SHOW_IMPLICIT");
+
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
}
/**
@@ -686,7 +759,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testFullScreenMode() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
Log.i(TAG, "Set orientation natural");
verifyFullscreenMode(() -> setOrientation(0), false /* eventExpected */,
@@ -723,25 +796,22 @@ public class InputMethodServiceTest {
public void testShowHideImeNavigationBar_doesDrawImeNavBar() {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Show IME
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(true /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue();
- // Try to hide IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(false /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is not shown after hide request")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
- // Try to show IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(true /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is shown after show request")
@@ -758,25 +828,22 @@ public class InputMethodServiceTest {
public void testShowHideImeNavigationBar_doesNotDrawImeNavBar() {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Show IME
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(false /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(false /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially not shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
- // Try to hide IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(false /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is not shown after hide request")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
- // Try to show IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(true /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is not shown after show request")
@@ -792,7 +859,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
@@ -818,7 +885,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
@@ -844,7 +911,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
final var info = mImm.getCurrentInputMethodInfo();
assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
@@ -855,7 +922,7 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(true /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
@@ -884,7 +951,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
final var info = mImm.getCurrentInputMethodInfo();
assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
@@ -893,7 +960,7 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(true /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
@@ -956,7 +1023,8 @@ public class InputMethodServiceTest {
runnable.run();
}
mInstrumentation.waitForIdleSync();
- eventCalled = latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ eventCalled = latch.await(eventExpected ? EXPECT_TIMEOUT_MS : NOT_EXCEPT_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("Interrupted while waiting for latch: " + e.getMessage());
return;
@@ -1016,10 +1084,8 @@ public class InputMethodServiceTest {
verifyInputViewStatus(runnable, EVENT_CONFIG, eventExpected, false /* shown */,
"IME is not shown");
if (eventExpected) {
- // Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
.that(TestActivity.getInstance()).isNotEqualTo(mActivity));
- // Get the new TestActivity.
mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
@@ -1062,6 +1128,7 @@ public class InputMethodServiceTest {
private void prepareActivity() {
mActivity = TestActivity.startSync(mInstrumentation);
+ mInstrumentation.waitForIdleSync();
Log.i(TAG, "Finish preparing activity with editor.");
}
@@ -1086,21 +1153,51 @@ public class InputMethodServiceTest {
* @param enable the value to be set.
*/
private void setShowImeWithHardKeyboard(boolean enable) {
+ if (mInputMethodService == null) {
+ // If the IME is no longer around, reset the setting unconditionally.
+ executeShellCommand(SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD + " " + (enable ? "1" : "0"));
+ return;
+ }
+
final boolean currentEnabled =
mInputMethodService.getShouldShowImeWithHardKeyboardForTesting();
if (currentEnabled != enable) {
- executeShellCommand(enable
- ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
- : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
+ executeShellCommand(SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD + " " + (enable ? "1" : "0"));
eventually(() -> assertWithMessage("showImeWithHardKeyboard updated")
.that(mInputMethodService.getShouldShowImeWithHardKeyboardForTesting())
.isEqualTo(enable));
}
}
- private static void executeShellCommand(@NonNull String cmd) {
+ /**
+ * Gets the verbose logging state in {@link android.view.inputmethod.ImeTracker}.
+ *
+ * @return {@code true} iff verbose logging is enabled.
+ */
+ private static boolean getVerboseImeTrackerLogging() {
+ return executeShellCommand(GET_VERBOSE_IME_TRACKER_LOGGING_CMD).trim().equals("1");
+ }
+
+ /**
+ * Sets verbose logging in {@link android.view.inputmethod.ImeTracker}.
+ *
+ * @param enabled whether to enable or disable verbose logging.
+ *
+ * @implNote This must use {@link ActivityManager#notifySystemPropertiesChanged()} to listen
+ * for changes to the system property for the verbose ImeTracker logging.
+ */
+ private void setVerboseImeTrackerLogging(boolean enabled) {
+ final var context = mInstrumentation.getContext();
+ final var am = context.getSystemService(ActivityManager.class);
+
+ executeShellCommand(SET_VERBOSE_IME_TRACKER_LOGGING_CMD + " " + (enabled ? "1" : "0"));
+ am.notifySystemPropertiesChanged();
+ }
+
+ @NonNull
+ private static String executeShellCommand(@NonNull String cmd) {
Log.i(TAG, "Run command: " + cmd);
- SystemUtil.runShellCommandOrThrow(cmd);
+ return SystemUtil.runShellCommandOrThrow(cmd);
}
/**
@@ -1113,8 +1210,7 @@ public class InputMethodServiceTest {
@NonNull
private UiObject2 getUiObject(@NonNull BySelector bySelector) {
- final var uiObject = mUiDevice.wait(Until.findObject(bySelector),
- TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS));
+ final var uiObject = mUiDevice.wait(Until.findObject(bySelector), TIMEOUT_MS);
assertWithMessage("UiObject with " + bySelector + " was found").that(uiObject).isNotNull();
return uiObject;
}
@@ -1137,10 +1233,10 @@ public class InputMethodServiceTest {
*
* <p>Note, neither of these are normally drawn when in three button navigation mode.
*
- * @param enabled whether the IME nav bar and IME Switcher button are drawn.
+ * @param enable whether the IME nav bar and IME Switcher button are drawn.
*/
- private void setDrawsImeNavBarAndSwitcherButton(boolean enabled) {
- final int flags = enabled ? IME_DRAWS_IME_NAV_BAR | SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0;
+ private void setDrawsImeNavBarAndSwitcherButton(boolean enable) {
+ final int flags = enable ? IME_DRAWS_IME_NAV_BAR | SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0;
mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(flags);
}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
index 558d1a7c4e8b..d4d4dcaa4f48 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -111,12 +111,6 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
@Override
- public void requestHideSelf(int flags) {
- Log.i(TAG, "requestHideSelf() " + flags);
- super.requestHideSelf(flags);
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfig) {
Log.i(TAG, "onConfigurationChanged() " + newConfig);
super.onConfigurationChanged(newConfig);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
index b5a538fa09f8..c7da27420cbb 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
@@ -103,7 +103,7 @@ public class AudioDeviceInventoryTest {
// NOTE: for now this is only when flag asDeviceConnectionFailure is true
if (asDeviceConnectionFailure()) {
when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT))
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false /*deviceSwitch*/))
.thenReturn(AudioSystem.AUDIO_STATUS_ERROR);
runWithBluetoothPrivilegedPermission(
() -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo,
@@ -115,7 +115,7 @@ public class AudioDeviceInventoryTest {
// test that the device is added when AudioSystem returns AUDIO_STATUS_OK
// when setDeviceConnectionState is called for the connection
when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT))
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false /*deviceSwitch*/))
.thenReturn(AudioSystem.AUDIO_STATUS_OK);
runWithBluetoothPrivilegedPermission(
() -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo,
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index ce59a86c6ca3..39e7d727f7c5 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -51,9 +51,9 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter {
// Overrides of AudioSystemAdapter
@Override
public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
- int codecFormat) {
- Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s",
- attributes.toString(), state, Integer.toHexString(codecFormat)));
+ int codecFormat, boolean deviceSwitch) {
+ Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s %b",
+ attributes.toString(), state, Integer.toHexString(codecFormat), deviceSwitch));
return AudioSystem.AUDIO_STATUS_OK;
}
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 82435b24dad6..6e109a8d4eaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -164,6 +164,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.function.BooleanSupplier;
/**
* Tests for the {@link DisplayContent} class.
@@ -2674,16 +2675,67 @@ public class DisplayContentTests extends WindowTestsBase {
public void testKeyguardGoingAwayWhileAodShown() {
mDisplayContent.getDisplayPolicy().setAwake(true);
- final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay(
- mDisplayContent).build();
- final ActivityRecord activity = appWin.mActivityRecord;
+ final KeyguardController keyguard = mAtm.mKeyguardController;
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final int displayId = mDisplayContent.getDisplayId();
+
+ final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId);
+ final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
+ final BooleanSupplier appVisible = activity::isVisibleRequested;
+
+ // Begin locked and in AOD
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertFalse(appVisible.getAsBoolean());
+
+ // Start unlocking from AOD.
+ keyguard.keyguardGoingAway(displayId, 0x0 /* flags */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
- mAtm.mKeyguardController.setKeyguardShown(appWin.getDisplayId(), true /* keyguardShowing */,
- true /* aodShowing */);
- assertFalse(activity.isVisibleRequested());
+ // Clear AOD. This does *not* clear the going-away status.
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+
+ // Finish unlock
+ keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+ }
+
+ @Test
+ public void testKeyguardGoingAwayCanceledWhileAodShown() {
+ mDisplayContent.getDisplayPolicy().setAwake(true);
- mAtm.mKeyguardController.keyguardGoingAway(appWin.getDisplayId(), 0 /* flags */);
- assertTrue(activity.isVisibleRequested());
+ final KeyguardController keyguard = mAtm.mKeyguardController;
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final int displayId = mDisplayContent.getDisplayId();
+
+ final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId);
+ final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
+ final BooleanSupplier appVisible = activity::isVisibleRequested;
+
+ // Begin locked and in AOD
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertFalse(appVisible.getAsBoolean());
+
+ // Start unlocking from AOD.
+ keyguard.keyguardGoingAway(displayId, 0x0 /* flags */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+
+ // Clear AOD. This does *not* clear the going-away status.
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+
+ // Same API call a second time cancels the unlock, because AOD isn't changing.
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
+ assertTrue(keyguardShowing.getAsBoolean());
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertFalse(appVisible.getAsBoolean());
}
@Test