summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp1
-rw-r--r--core/java/android/app/supervision/ISupervisionAppService.aidl2
-rw-r--r--core/java/android/app/supervision/SupervisionAppService.java19
-rw-r--r--core/java/android/content/res/AssetManager.java55
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java11
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java34
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/android/security/flags.aconfig9
-rw-r--r--core/java/android/view/IWindowManager.aidl3
-rw-r--r--core/java/android/view/SurfaceControl.java7
-rw-r--r--core/java/android/view/SurfaceControlRegistry.java10
-rw-r--r--core/java/android/view/ViewRootImpl.java3
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java11
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java3
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java119
-rw-r--r--core/java/android/view/contentcapture/flags/content_capture_flags.aconfig12
-rw-r--r--core/java/android/widget/Magnifier.java5
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/res/values/config_telephony.xml2
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java53
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java6
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt (renamed from libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt86
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt97
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt45
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt146
-rw-r--r--media/java/android/media/quality/MediaQualityManager.java36
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt6
-rw-r--r--packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml25
-rw-r--r--packages/SystemUI/res/values/strings.xml13
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/OWNERS2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OWNERS6
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java112
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java32
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java133
-rw-r--r--services/core/java/com/android/server/am/UserController.java3
-rw-r--r--services/core/java/com/android/server/am/UserSwitchingDialog.java46
-rw-r--r--services/core/java/com/android/server/display/ColorFade.java5
-rw-r--r--services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java16
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java33
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java567
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java2
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java13
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java13
-rw-r--r--services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java5
-rw-r--r--services/core/java/com/android/server/wm/StrictModeFlash.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java48
-rw-r--r--services/core/java/com/android/server/wm/Watermark.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java76
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java90
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java256
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java57
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java7
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java62
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt72
-rw-r--r--tests/NetworkSecurityConfigTest/Android.bp3
-rw-r--r--tests/NetworkSecurityConfigTest/TEST_MAPPING7
91 files changed, 2440 insertions, 658 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f249884cb1a0..0a61df71cc29 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -1409,6 +1409,7 @@ java_aconfig_library {
// Content Capture
aconfig_declarations {
name: "android.view.contentcapture.flags-aconfig",
+ exportable: true,
package: "android.view.contentcapture.flags",
container: "system",
srcs: ["core/java/android/view/contentcapture/flags/*.aconfig"],
diff --git a/core/java/android/app/supervision/ISupervisionAppService.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl
index 033998fc4a5b..207fab90d015 100644
--- a/core/java/android/app/supervision/ISupervisionAppService.aidl
+++ b/core/java/android/app/supervision/ISupervisionAppService.aidl
@@ -20,4 +20,6 @@ package android.app.supervision;
* @hide
*/
interface ISupervisionAppService {
+ void onEnabled();
+ void onDisabled();
}
diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java
index 4468c78cbd34..4530be5c270a 100644
--- a/core/java/android/app/supervision/SupervisionAppService.java
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -28,10 +28,29 @@ import android.os.IBinder;
*/
public class SupervisionAppService extends Service {
private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() {
+ @Override
+ public void onEnabled() {
+ SupervisionAppService.this.onEnabled();
+ }
+
+ @Override
+ public void onDisabled() {
+ SupervisionAppService.this.onDisabled();
+ }
};
@Override
public final IBinder onBind(Intent intent) {
return mBinder.asBinder();
}
+
+ /**
+ * Called when supervision is enabled.
+ */
+ public void onEnabled() {}
+
+ /**
+ * Called when supervision is disabled.
+ */
+ public void onDisabled() {}
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bbfae8117b16..7cd2d31ac974 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -62,6 +62,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Provides access to an application's raw asset files; see {@link Resources}
@@ -133,7 +134,7 @@ public final class AssetManager implements AutoCloseable {
// Debug/reference counting implementation.
@GuardedBy("this") private boolean mOpen = true;
- @GuardedBy("this") private int mNumRefs = 1;
+ private AtomicInteger mNumRefs = new AtomicInteger(1);
@GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
private ResourcesLoader[] mLoaders;
@@ -244,7 +245,7 @@ public final class AssetManager implements AutoCloseable {
mObject = nativeCreate();
if (DEBUG_REFS) {
- mNumRefs = 0;
+ mNumRefs.set(0);
incRefsLocked(hashCode());
}
@@ -260,7 +261,7 @@ public final class AssetManager implements AutoCloseable {
private AssetManager(boolean sentinel) {
mObject = nativeCreate();
if (DEBUG_REFS) {
- mNumRefs = 0;
+ mNumRefs.set(0);
incRefsLocked(hashCode());
}
}
@@ -324,7 +325,7 @@ public final class AssetManager implements AutoCloseable {
}
mOpen = false;
- decRefsLocked(hashCode());
+ decRefs(hashCode());
}
}
@@ -1235,9 +1236,7 @@ public final class AssetManager implements AutoCloseable {
}
void xmlBlockGone(int id) {
- synchronized (this) {
- decRefsLocked(id);
- }
+ decRefs(id);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1308,9 +1307,7 @@ public final class AssetManager implements AutoCloseable {
}
void releaseTheme(long themePtr) {
- synchronized (this) {
- decRefsLocked(themePtr);
- }
+ decRefs(themePtr);
}
static long getThemeFreeFunction() {
@@ -1332,7 +1329,7 @@ public final class AssetManager implements AutoCloseable {
if (this != newAssetManager) {
synchronized (this) {
ensureValidLocked();
- decRefsLocked(themePtr);
+ decRefs(themePtr);
}
synchronized (newAssetManager) {
newAssetManager.ensureValidLocked();
@@ -1364,8 +1361,8 @@ public final class AssetManager implements AutoCloseable {
@Override
protected void finalize() throws Throwable {
- if (DEBUG_REFS && mNumRefs != 0) {
- Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs);
+ if (DEBUG_REFS && mNumRefs.get() != 0) {
+ Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs.get());
if (mRefStacks != null) {
for (RuntimeException e : mRefStacks.values()) {
Log.w(TAG, "Reference from here", e);
@@ -1473,9 +1470,7 @@ public final class AssetManager implements AutoCloseable {
nativeAssetDestroy(mAssetNativePtr);
mAssetNativePtr = 0;
- synchronized (AssetManager.this) {
- decRefsLocked(hashCode());
- }
+ decRefs(hashCode());
}
}
@@ -1680,19 +1675,25 @@ public final class AssetManager implements AutoCloseable {
RuntimeException ex = new RuntimeException();
mRefStacks.put(id, ex);
}
- mNumRefs++;
+ mNumRefs.incrementAndGet();
}
- @GuardedBy("this")
- private void decRefsLocked(long id) {
- if (DEBUG_REFS && mRefStacks != null) {
- mRefStacks.remove(id);
- }
- mNumRefs--;
- if (mNumRefs == 0 && mObject != 0) {
- nativeDestroy(mObject);
- mObject = 0;
- mApkAssets = sEmptyApkAssets;
+ private void decRefs(long id) {
+ if (DEBUG_REFS) {
+ synchronized (this) {
+ if (mRefStacks != null) {
+ mRefStacks.remove(id);
+ }
+ }
+ }
+ if (mNumRefs.decrementAndGet() == 0) {
+ synchronized (this) {
+ if (mNumRefs.get() == 0 && mObject != 0) {
+ nativeDestroy(mObject);
+ mObject = 0;
+ mApkAssets = sEmptyApkAssets;
+ }
+ }
}
}
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 88d69b665c87..030c883924a7 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -182,17 +182,10 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {
setOpenParamsBuilder(openParamsBuilder);
Object lock = null;
- if (mName == null || !Flags.concurrentOpenHelper()) {
+ if (!Flags.concurrentOpenHelper() || mName == null) {
lock = new Object();
} else {
- try {
- final String path = mContext.getDatabasePath(mName).getCanonicalPath();
- lock = sDbLock.computeIfAbsent(path, (String k) -> new Object());
- } catch (IOException e) {
- Log.d(TAG, "failed to construct db path for " + mName);
- // Ensure the lock is not null.
- lock = new Object();
- }
+ lock = sDbLock.computeIfAbsent(mName, (String k) -> new Object());
}
mLock = lock;
}
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 4025242fd208..1a712d2b3f31 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -122,17 +122,11 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69;
public static final int KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW = 70;
public static final int KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW = 71;
- public static final int KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN = 72;
- public static final int KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT = 73;
- public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74;
- public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75;
- public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 76;
- public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 77;
- public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT = 78;
- public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT = 79;
- public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP = 80;
- public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN = 81;
- public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 82;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 72;
+ public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 73;
+ public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 74;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 75;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 76;
public static final int FLAG_CANCELLED = 1;
@@ -220,16 +214,10 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW,
KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
- KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN,
- KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT,
KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB,
- KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT,
- KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT,
- KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP,
- KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN,
KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS,
})
@Retention(RetentionPolicy.SOURCE)
@@ -815,10 +803,6 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW";
case KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW:
return "KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW";
- case KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN:
- return "KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN";
- case KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT:
- return "KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT";
case KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION";
case KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
@@ -827,14 +811,6 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW";
case KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB:
return "KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB";
- case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT:
- return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT";
- case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT:
- return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT";
- case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP:
- return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP";
- case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN:
- return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN";
case KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
return "KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS";
default:
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0cfec2cc7314..73d1e1701eec 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12869,6 +12869,19 @@ public final class Settings {
*/
public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows";
+ /**
+ * Controls if the adaptive authentication feature should be disabled, which
+ * will attempt to lock the device after a number of consecutive authentication
+ * attempts fail.
+ *
+ * This can only be disabled on debuggable builds. Set to 1 to disable or 0 for the
+ * normal behavior.
+ *
+ * @hide
+ */
+ public static final String DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK =
+ "disable_adaptive_auth_limit_lock";
+
/** @hide */
public static final int PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK = 0;
/** @hide */
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 4a9e945e62a9..a5586227cbb3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -155,4 +155,11 @@ flag {
description: "Feature flag to add the privileged flag to the SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE permission"
bug: "380120712"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "disable_adaptive_auth_counter_lock"
+ namespace: "biometrics"
+ description: "Flag to allow an adb secure setting to disable the adaptive auth lock"
+ bug: "371057865"
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 028043340732..06eb0428bfcf 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -227,9 +227,6 @@ interface IWindowManager
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void endProlongedAnimations();
- void startFreezingScreen(int exitAnim, int enterAnim);
- void stopFreezingScreen();
-
// these require DISABLE_KEYGUARD permission
/** @deprecated use Activity.setShowWhenLocked instead. */
void disableKeyguard(IBinder token, String tag, int userId);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d7cf3e827695..311fbee2fc4b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3026,6 +3026,7 @@ public final class SurfaceControl implements Parcelable {
// Only non-null if the SurfaceControlRegistry is enabled. This list tracks the set of calls
// made through this transaction object, and is dumped (and cleared) when the transaction is
// later applied.
+ @Nullable
ArrayList<String> mCalls;
Runnable mFreeNativeResources;
@@ -4898,8 +4899,10 @@ public final class SurfaceControl implements Parcelable {
SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
"merge", this, null, "otherTx=" + other.getId());
if (mCalls != null) {
- mCalls.addAll(other.mCalls);
- other.mCalls.clear();
+ if (other.mCalls != null) {
+ mCalls.addAll(other.mCalls);
+ other.mCalls.clear();
+ }
}
}
mResizedSurfaces.putAll(other.mResizedSurfaces);
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 121c01be7294..0b528bffe5c5 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -334,13 +334,17 @@ public class SurfaceControlRegistry {
if (call == APPLY) {
// Log the apply and dump the calls on that transaction
Log.e(TAG, msg, new Throwable());
- for (int i = 0; i < tx.mCalls.size(); i++) {
- Log.d(TAG, " " + tx.mCalls.get(i));
+ if (tx.mCalls != null) {
+ for (int i = 0; i < tx.mCalls.size(); i++) {
+ Log.d(TAG, " " + tx.mCalls.get(i));
+ }
}
} else if (matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) {
// Otherwise log this call to the transaction if it matches the tracked calls
Log.e(TAG, msg, new Throwable());
- tx.mCalls.add(msg);
+ if (tx.mCalls != null) {
+ tx.mCalls.add(msg);
+ }
}
} else {
// Log this call if it matches the tracked calls
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0972c01aa653..64e7becb1ed4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5552,6 +5552,9 @@ public final class ViewRootImpl implements ViewParent,
if (mAttachInfo.mContentCaptureManager != null) {
ContentCaptureSession session =
mAttachInfo.mContentCaptureManager.getMainContentCaptureSession();
+ if (android.view.contentcapture.flags.Flags.postCreateAndroidBgThread()) {
+ session.performStart();
+ }
session.notifyWindowBoundsChanged(session.getId(),
getConfiguration().windowConfiguration.getBounds());
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 724e8fa830af..3f3484d5a527 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,7 +52,6 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.RingBuffer;
import com.android.internal.util.SyncResultReceiver;
@@ -605,7 +604,6 @@ public final class ContentCaptureManager {
mContext,
this,
prepareUiHandler(),
- prepareContentCaptureHandler(),
mService
);
if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
@@ -616,15 +614,6 @@ public final class ContentCaptureManager {
@NonNull
@GuardedBy("mLock")
- private Handler prepareContentCaptureHandler() {
- if (mContentCaptureHandler == null) {
- mContentCaptureHandler = BackgroundThread.getHandler();
- }
- return mContentCaptureHandler;
- }
-
- @NonNull
- @GuardedBy("mLock")
private Handler prepareUiHandler() {
if (mUiHandler == null) {
mUiHandler = Handler.createAsync(Looper.getMainLooper());
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 9aeec20ec9b7..6bb2975d9cf1 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -286,6 +286,9 @@ public abstract class ContentCaptureSession implements AutoCloseable {
abstract void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@NonNull ComponentName component, int flags);
+ /** @hide */
+ public void performStart() {}
+
abstract boolean isDisabled();
/**
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 2fb78c038ca2..eddfc42da9bd 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -57,10 +57,12 @@ import android.view.View;
import android.view.ViewStructure;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentcapture.flags.Flags;
import android.view.contentprotection.ContentProtectionEventProcessor;
import android.view.inputmethod.BaseInputConnection;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.IResultReceiver;
import com.android.modules.expresslog.Counter;
@@ -107,8 +109,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@NonNull
private final Handler mUiHandler;
- @NonNull
- private final Handler mContentCaptureHandler;
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public Handler mContentCaptureHandler;
/**
* Interface to the system_server binder object - it's only used to start the session (and
@@ -187,6 +191,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@Nullable
public ContentProtectionEventProcessor mContentProtectionEventProcessor;
+ /**
+ * A runnable object to perform the start of this session.
+ */
+ @Nullable
+ private Runnable mStartRunnable = null;
+
private static class SessionStateReceiver extends IResultReceiver.Stub {
private final WeakReference<MainContentCaptureSession> mMainSession;
@@ -198,7 +208,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
public void send(int resultCode, Bundle resultData) {
final MainContentCaptureSession mainSession = mMainSession.get();
if (mainSession == null) {
- Log.w(TAG, "received result after mina session released");
+ Log.w(TAG, "received result after main session released");
return;
}
final IBinder binder;
@@ -213,6 +223,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
binder = resultData.getBinder(EXTRA_BINDER);
if (binder == null) {
Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
+ // explicitly init the bg thread
+ mainSession.mContentCaptureHandler = mainSession.prepareContentCaptureHandler();
mainSession.runOnContentCaptureThread(() -> mainSession.resetSession(
STATE_DISABLED | STATE_INTERNAL_ERROR));
return;
@@ -220,23 +232,45 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
} else {
binder = null;
}
+ // explicitly init the bg thread
+ mainSession.mContentCaptureHandler = mainSession.prepareContentCaptureHandler();
mainSession.runOnContentCaptureThread(() ->
mainSession.onSessionStarted(resultCode, binder));
}
}
+ /**
+ * Prepares the content capture handler(i.e. the background thread).
+ *
+ * This is expected to be called from the {@link SessionStateReceiver#send} callback, after the
+ * session {@link performStart}. This is expected to be executed in a binder thread, instead
+ * of the UI thread.
+ */
+ @NonNull
+ private Handler prepareContentCaptureHandler() {
+ if (mContentCaptureHandler == null) {
+ try {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareContentCaptureHandler");
+ }
+ mContentCaptureHandler = BackgroundThread.getHandler();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+ return mContentCaptureHandler;
+ }
+
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
public MainContentCaptureSession(
@NonNull ContentCaptureManager.StrippedContext context,
@NonNull ContentCaptureManager manager,
@NonNull Handler uiHandler,
- @NonNull Handler contentCaptureHandler,
@NonNull IContentCaptureManager systemServerInterface) {
mContext = context;
mManager = manager;
mUiHandler = uiHandler;
- mContentCaptureHandler = contentCaptureHandler;
mSystemServerInterface = systemServerInterface;
final int logHistorySize = mManager.mOptions.logHistorySize;
@@ -260,18 +294,49 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
/**
- * Starts this session.
+ * Performs the start of the session.
+ *
+ * This is expected to be called from the UI thread, when the activity finishes its first frame.
+ * This is a no-op if the session has already been started.
+ *
+ * See {@link #start(IBinder, IBinder, ComponentName, int)} for more details.
+ *
+ * @hide */
+ @Override
+ public void performStart() {
+ if (!hasStarted() && mStartRunnable != null) {
+ mStartRunnable.run();
+ }
+ }
+
+ /**
+ * Creates a runnable to start this session.
+ *
+ * For performance reasons, it is better to only create a task to start the session
+ * during the creation of the activity and perform the actual start when the activity
+ * finishes it's first frame.
*/
@Override
void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@NonNull ComponentName component, int flags) {
- runOnContentCaptureThread(
- () -> startImpl(token, shareableActivityToken, component, flags));
+ if (Flags.postCreateAndroidBgThread()) {
+ mStartRunnable = () -> {
+ try {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "cc session startImpl");
+ }
+ startImpl(token, shareableActivityToken, component, flags);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ };
+ } else {
+ startImpl(token, shareableActivityToken, component, flags);
+ }
}
private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@NonNull ComponentName component, int flags) {
- checkOnContentCaptureThread();
if (!isContentCaptureEnabled()) return;
if (sVerbose) {
@@ -305,6 +370,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e);
}
}
+
@Override
void onDestroy() {
clearAndRunOnContentCaptureThread(() -> {
@@ -561,7 +627,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
private boolean hasStarted() {
- checkOnContentCaptureThread();
return mState != UNKNOWN_STATE;
}
@@ -575,6 +640,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet");
return;
}
+ if (mContentCaptureHandler == null) {
+ Log.w(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): content capture "
+ + "thread not ready");
+ return;
+ }
if (mDisabled.get()) {
// Should not be called on this state, as handleSendEvent checks.
@@ -647,6 +717,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
if (!isContentCaptureReceiverEnabled()) {
return;
}
+ if (mContentCaptureHandler == null) {
+ Log.w(TAG, "handleForceFlush(" + getDebugState(reason) + "): content capture thread"
+ + "not ready");
+ return;
+ }
if (mDirectServiceInterface == null) {
if (sVerbose) {
@@ -763,7 +838,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
mDirectServiceInterface = null;
mContentProtectionEventProcessor = null;
- mContentCaptureHandler.removeMessages(MSG_FLUSH);
+ if (mContentCaptureHandler != null) {
+ mContentCaptureHandler.removeMessages(MSG_FLUSH);
+ }
}
@Override
@@ -917,6 +994,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
* clear the buffer events then starting sending out current event.
*/
private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
+ if (mContentCaptureHandler == null) {
+ mEventProcessQueue.offer(event);
+ return;
+ }
if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) {
// The buffer events are cleared in the same thread first to prevent new events
// being added during the time of context switch. This would disrupt the sequence
@@ -1119,6 +1200,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
* always delegate to the assigned thread from {@code mHandler} for synchronization.</p>
*/
private void checkOnContentCaptureThread() {
+ if (mContentCaptureHandler == null) {
+ Log.e(TAG, "content capture thread is not initiallized!");
+ return;
+ }
final boolean onContentCaptureThread = mContentCaptureHandler.getLooper().isCurrentThread();
if (!onContentCaptureThread) {
mWrongThreadCount.incrementAndGet();
@@ -1139,6 +1224,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
* </p>
*/
private void runOnContentCaptureThread(@NonNull Runnable r) {
+ if (mContentCaptureHandler == null) {
+ Log.e(TAG, "content capture thread is not initiallized!");
+ // fall back to UI thread
+ runOnUiThread(r);
+ return;
+ }
if (!mContentCaptureHandler.getLooper().isCurrentThread()) {
mContentCaptureHandler.post(r);
} else {
@@ -1147,6 +1238,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) {
+ if (mContentCaptureHandler == null) {
+ Log.e(TAG, "content capture thread is not initiallized!");
+ // fall back to UI thread
+ runOnUiThread(r);
+ return;
+ }
if (!mContentCaptureHandler.getLooper().isCurrentThread()) {
mContentCaptureHandler.removeMessages(what);
mContentCaptureHandler.post(r);
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index f709ed7f57cd..9df835098268 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -13,4 +13,16 @@ flag {
namespace: "machine_learning"
description: "Feature flag for baklava content capture API"
bug: "380381249"
+ is_exported: true
+}
+
+flag {
+ name: "post_create_android_bg_thread"
+ namespace: "pixel_state_server"
+ description: "Feature flag to post create the bg thread when an app is in the allowlist"
+ bug: "376468525"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 33890b80869d..f70bf9737636 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -1021,8 +1021,9 @@ public final class Magnifier {
.setCallsite("InternalPopupWindow")
.build();
- mBBQ = new BLASTBufferQueue("magnifier surface", mBbqSurfaceControl,
- surfaceWidth, surfaceHeight, PixelFormat.TRANSLUCENT);
+ mBBQ = new BLASTBufferQueue("magnifier surface", /*updateDestinationFrame*/ true);
+ mBBQ.update(mBbqSurfaceControl,
+ surfaceWidth, surfaceHeight, PixelFormat.TRANSLUCENT);
mSurface = mBBQ.createSurface();
// Setup the RenderNode tree. The root has two children, one containing the bitmap
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 445080215017..73279700ecb1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -180,6 +180,7 @@
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
<protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ENCRYPTION_CHANGE" />
<protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
<protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" />
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 666f1cf39fe3..965c69d16d79 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -84,7 +84,7 @@
CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY.
If 0, the device always switch to the higher score SIM.
If < 0, the network type and signal strength based auto switch is disabled. -->
- <integer name="auto_data_switch_score_tolerance">-1</integer>
+ <integer name="auto_data_switch_score_tolerance">4000</integer>
<java-symbol type="integer" name="auto_data_switch_score_tolerance" />
<!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index b42bcee77c67..4a5123ec0663 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -499,6 +499,57 @@ public class MainContentCaptureSessionTest {
assertThat(session.mEventProcessQueue).hasSize(1);
}
+ @Test
+ public void notifyContentCaptureEvents_beforeSessionPerformStart() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSession session = createSession(options);
+ session.mContentCaptureHandler = null;
+ session.mDirectServiceInterface = null;
+
+ notifyContentCaptureEvents(session);
+ mTestableLooper.processAllMessages();
+
+ assertThat(session.mEvents).isNull();
+ assertThat(session.mEventProcessQueue).hasSize(7); // 5 view events + 2 view tree events
+ }
+
+ @Test
+ public void notifyViewAppeared_beforeSessionPerformStart() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSession session = createSession(options);
+ session.mContentCaptureHandler = null;
+ session.mDirectServiceInterface = null;
+
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+
+ assertThat(session.mEvents).isNull();
+ assertThat(session.mEventProcessQueue).hasSize(1);
+ }
+
+ @Test
+ public void flush_beforeSessionPerformStart() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSession session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mContentCaptureHandler = null;
+ session.mDirectServiceInterface = null;
+
+ session.flush(REASON);
+
+ assertThat(session.mEvents).hasSize(1);
+ assertThat(session.mEventProcessQueue).isEmpty();
+ }
+
/** Simulates the regular content capture events sequence. */
private void notifyContentCaptureEvents(final MainContentCaptureSession session) {
final ArrayList<Object> events = new ArrayList<>(
@@ -561,8 +612,8 @@ public class MainContentCaptureSessionTest {
sStrippedContext,
manager,
testHandler,
- testHandler,
mMockSystemServerInterface);
+ session.mContentCaptureHandler = testHandler;
session.mComponentName = COMPONENT_NAME;
return session;
}
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 906c71d9caca..1c34e0d54908 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -66,12 +66,6 @@ public final class BLASTBufferQueue {
}
/** Create a new connection with the surface flinger. */
- public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
- @PixelFormat.Format int format) {
- this(name, true /* updateDestinationFrame */);
- update(sc, width, height, format);
- }
-
public BLASTBufferQueue(String name, boolean updateDestinationFrame) {
mNativeObject = nativeCreate(name, updateDestinationFrame);
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index c62d2a06bad5..90ea7d35015e 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -34,7 +34,6 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.wm.shell.Flags
import com.android.wm.shell.ShellTaskOrganizer
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.properties.ProdBubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
@@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index ab2e552c7a3d..a7eebd6159e4 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -36,10 +36,10 @@ import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
index 3043e2bcb0be..a83327bbadee 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -35,7 +35,6 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.ShellTaskOrganizer
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
@@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
index bcaa63bfad36..750178678785 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
@@ -22,9 +22,9 @@ import android.content.res.Resources
import android.view.LayoutInflater
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.wm.shell.R
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
+import com.android.wm.shell.common.TestShellExecutor
import com.google.common.util.concurrent.MoreExecutors.directExecutor
/** Helper to create a [Bubble] instance */
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index 117ede0d0ac8..9e58b5be9d0d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -36,7 +36,6 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
@@ -46,6 +45,7 @@ import com.android.wm.shell.bubbles.BubbleTaskView
import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index bfc798bb9c79..fbbcff2dee92 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -33,7 +33,6 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
@@ -44,6 +43,7 @@ import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.RegionSamplingProvider
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 9b1645e9534c..5c5dde7da351 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -36,7 +36,6 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.wm.shell.R
import com.android.wm.shell.ShellTaskOrganizer
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.bubbles.BubbleData
@@ -58,6 +57,7 @@ import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt
index ef8e71c2590b..6b549b42cdcd 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell
-
-import com.android.wm.shell.common.ShellExecutor
+package com.android.wm.shell.common
/**
* Simple implementation of [ShellExecutor] that collects all runnables and executes them
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
index 67592e60e954..0d0bc9be72b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.common
import android.app.PendingIntent
+import android.app.TaskInfo
import android.content.ComponentName
import android.content.Intent
import com.android.wm.shell.ShellTaskOrganizer
@@ -34,7 +35,11 @@ object ComponentUtils {
/** Retrieves the package name from a [taskId]. */
@JvmStatic
fun getPackageName(taskId: Int, taskOrganizer: ShellTaskOrganizer): String? {
- val taskInfo = taskOrganizer.getRunningTaskInfo(taskId)
- return getPackageName(taskInfo?.baseIntent)
+ val taskInfo = taskOrganizer.getRunningTaskInfo(taskId) ?: return null
+ return getPackageName(taskInfo)
}
+
+ /** Retrieves the package name from a [TaskInfo]. */
+ @JvmStatic
+ fun getPackageName(taskInfo: TaskInfo): String? = getPackageName(taskInfo.baseIntent)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt
new file mode 100644
index 000000000000..a13ad20f8c05
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+
+/**
+ * Utility class for calculating bounds during multi-display drag operations.
+ *
+ * This class provides helper functions to perform bounds calculation during window drag.
+ */
+object MultiDisplayDragMoveBoundsCalculator {
+ /**
+ * Calculates the global DP bounds of a window being dragged across displays.
+ *
+ * @param startDisplayLayout The DisplayLayout object of the display where the drag started.
+ * @param repositionStartPoint The starting position of the drag (in pixels), relative to the
+ * display where the drag started.
+ * @param boundsAtDragStart The initial bounds of the window (in pixels), relative to the
+ * display where the drag started.
+ * @param currentDisplayLayout The DisplayLayout object of the display where the pointer is
+ * currently located.
+ * @param x The current x-coordinate of the drag pointer (in pixels).
+ * @param y The current y-coordinate of the drag pointer (in pixels).
+ * @return A RectF object representing the calculated global DP bounds of the window.
+ */
+ fun calculateGlobalDpBoundsForDrag(
+ startDisplayLayout: DisplayLayout,
+ repositionStartPoint: PointF,
+ boundsAtDragStart: Rect,
+ currentDisplayLayout: DisplayLayout,
+ x: Float,
+ y: Float,
+ ): RectF {
+ // Convert all pixel values to DP.
+ val startCursorDp =
+ startDisplayLayout.localPxToGlobalDp(repositionStartPoint.x, repositionStartPoint.y)
+ val currentCursorDp = currentDisplayLayout.localPxToGlobalDp(x, y)
+ val startLeftTopDp =
+ startDisplayLayout.localPxToGlobalDp(boundsAtDragStart.left, boundsAtDragStart.top)
+ val widthDp = startDisplayLayout.pxToDp(boundsAtDragStart.width())
+ val heightDp = startDisplayLayout.pxToDp(boundsAtDragStart.height())
+
+ // Calculate DP bounds based on pointer movement delta.
+ val currentLeftDp = startLeftTopDp.x + (currentCursorDp.x - startCursorDp.x)
+ val currentTopDp = startLeftTopDp.y + (currentCursorDp.y - startCursorDp.y)
+ val currentRightDp = currentLeftDp + widthDp
+ val currentBottomDp = currentTopDp + heightDp
+
+ return RectF(currentLeftDp, currentTopDp, currentRightDp, currentBottomDp)
+ }
+
+ /**
+ * Converts global DP bounds to local pixel bounds for a specific display.
+ *
+ * @param rectDp The global DP bounds to convert.
+ * @param displayLayout The DisplayLayout representing the display to convert the bounds to.
+ * @return A Rect object representing the local pixel bounds on the specified display.
+ */
+ fun convertGlobalDpToLocalPxForRect(rectDp: RectF, displayLayout: DisplayLayout): Rect {
+ val leftTopPxDisplay = displayLayout.globalDpToLocalPx(rectDp.left, rectDp.top)
+ val rightBottomPxDisplay = displayLayout.globalDpToLocalPx(rectDp.right, rectDp.bottom)
+ return Rect(
+ leftTopPxDisplay.x.toInt(),
+ leftTopPxDisplay.y.toInt(),
+ rightBottomPxDisplay.x.toInt(),
+ rightBottomPxDisplay.y.toInt(),
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f38957e48dbf..3d57038b27fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -2425,6 +2425,25 @@ class DesktopTasksController(
// Update task bounds so that the task position will match the position of its leash
val wct = WindowContainerTransaction()
wct.setBounds(taskInfo.token, destinationBounds)
+
+ // TODO: b/362720497 - reparent to a specific desk within the target display.
+ // Reparent task if it has been moved to a new display.
+ if (Flags.enableConnectedDisplaysWindowDrag()) {
+ val newDisplayId = motionEvent.getDisplayId()
+ if (newDisplayId != taskInfo.getDisplayId()) {
+ val displayAreaInfo =
+ rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
+ if (displayAreaInfo == null) {
+ logW(
+ "Task reparent cannot find DisplayAreaInfo for displayId=%d",
+ newDisplayId,
+ )
+ } else {
+ wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true)
+ }
+ }
+ }
+
transitions.startTransition(TRANSIT_CHANGE, wct, null)
releaseVisualIndicator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 055bc8f5f092..d6d393f2500c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -99,6 +99,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.common.ComponentUtils;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -1926,14 +1927,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// instances, then refer to the list's size and reuse the list for Manage Windows menu.
final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService();
try {
+ // TODO(b/389184897): Move the following into a helper method of
+ // RecentsTasksController, similar to #findTaskInBackground.
+ final String packageName = ComponentUtils.getPackageName(info);
return activityTaskManager.getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_WITH_EXCLUDED,
info.userId).getList().stream().filter(
- recentTaskInfo -> (recentTaskInfo.taskId != info.taskId
- && recentTaskInfo.baseActivity != null
- && recentTaskInfo.baseActivity.getPackageName()
- .equals(info.baseActivity.getPackageName())
- )
+ recentTaskInfo -> {
+ if (recentTaskInfo.taskId == info.taskId) {
+ return false;
+ }
+ final String recentTaskPackageName =
+ ComponentUtils.getPackageName(recentTaskInfo);
+ return packageName != null
+ && packageName.equals(recentTaskPackageName);
+ }
).toList().size();
} catch (RemoteException e) {
throw new RuntimeException(e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index 8dc921c986ce..07496eb0e526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -31,6 +31,7 @@ import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.concurrent.TimeUnit
@@ -69,6 +70,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
@DragPositioningCallback.CtrlType private var ctrlType = 0
private var isResizingOrAnimatingResize = false
@Surface.Rotation private var rotation = 0
+ private var startDisplayId = 0
constructor(
taskOrganizer: ShellTaskOrganizer,
@@ -95,6 +97,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
this.ctrlType = ctrlType
+ startDisplayId = displayId
taskBoundsAtDragStart.set(
desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds
)
@@ -160,16 +163,47 @@ class MultiDisplayVeiledResizeTaskPositioner(
interactionJankMonitor.begin(
createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
)
+
val t = transactionSupplier.get()
- DragPositioningCallbackUtility.setPositionOnDrag(
- desktopWindowDecoration,
- repositionTaskBounds,
- taskBoundsAtDragStart,
- repositionStartPoint,
- t,
- x,
- y,
- )
+ val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
+ val currentDisplayLayout = displayController.getDisplayLayout(displayId)
+
+ if (startDisplayLayout == null || currentDisplayLayout == null) {
+ // Fall back to single-display drag behavior if any display layout is unavailable.
+ DragPositioningCallbackUtility.setPositionOnDrag(
+ desktopWindowDecoration,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ t,
+ x,
+ y,
+ )
+ } else {
+ val boundsDp =
+ MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
+ startDisplayLayout,
+ repositionStartPoint,
+ taskBoundsAtDragStart,
+ currentDisplayLayout,
+ x,
+ y,
+ )
+ repositionTaskBounds.set(
+ MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
+ boundsDp,
+ startDisplayLayout,
+ )
+ )
+
+ // TODO(b/383069173): Render drag indicator(s)
+
+ t.setPosition(
+ desktopWindowDecoration.mTaskSurface,
+ repositionTaskBounds.left.toFloat(),
+ repositionTaskBounds.top.toFloat(),
+ )
+ }
t.setFrameTimeline(Choreographer.getInstance().vsyncId)
t.apply()
}
@@ -200,13 +234,38 @@ class MultiDisplayVeiledResizeTaskPositioner(
}
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
} else {
- DragPositioningCallbackUtility.updateTaskBounds(
- repositionTaskBounds,
- taskBoundsAtDragStart,
- repositionStartPoint,
- x,
- y,
- )
+ val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
+ val currentDisplayLayout = displayController.getDisplayLayout(displayId)
+
+ if (startDisplayLayout == null || currentDisplayLayout == null) {
+ // Fall back to single-display drag behavior if any display layout is unavailable.
+ DragPositioningCallbackUtility.updateTaskBounds(
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ x,
+ y,
+ )
+ } else {
+ val boundsDp =
+ MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
+ startDisplayLayout,
+ repositionStartPoint,
+ taskBoundsAtDragStart,
+ currentDisplayLayout,
+ x,
+ y,
+ )
+ repositionTaskBounds.set(
+ MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
+ boundsDp,
+ currentDisplayLayout,
+ )
+ )
+
+ // TODO(b/383069173): Clear drag indicator(s)
+ }
+
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt
new file mode 100644
index 000000000000..bd924c2b47c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.content.res.Configuration
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.testing.TestableResources
+import com.android.wm.shell.ShellTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests for [MultiDisplayDragMoveBoundsCalculator].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveBoundsCalculatorTest
+ */
+class MultiDisplayDragMoveBoundsCalculatorTest : ShellTestCase() {
+ private lateinit var resources: TestableResources
+
+ @Before
+ fun setUp() {
+ resources = mContext.getOrCreateTestableResources()
+ val configuration = Configuration()
+ configuration.uiMode = 0
+ resources.overrideConfiguration(configuration)
+ }
+
+ @Test
+ fun testCalculateGlobalDpBoundsForDrag() {
+ val repositionStartPoint = PointF(20f, 40f)
+ val boundsAtDragStart = Rect(10, 20, 110, 120)
+ val x = 300f
+ val y = 400f
+ val displayLayout0 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0,
+ MultiDisplayTestUtil.DISPLAY_DPI_0,
+ resources.resources,
+ )
+ val displayLayout1 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
+ MultiDisplayTestUtil.DISPLAY_DPI_1,
+ resources.resources,
+ )
+
+ val actualBoundsDp =
+ MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
+ displayLayout0,
+ repositionStartPoint,
+ boundsAtDragStart,
+ displayLayout1,
+ x,
+ y,
+ )
+
+ val expectedBoundsDp = RectF(240f, -820f, 340f, -720f)
+ assertEquals(expectedBoundsDp, actualBoundsDp)
+ }
+
+ @Test
+ fun testConvertGlobalDpToLocalPxForRect() {
+ val displayLayout =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
+ MultiDisplayTestUtil.DISPLAY_DPI_1,
+ resources.resources,
+ )
+ val rectDp = RectF(150f, -350f, 300f, -250f)
+
+ val actualBoundsPx =
+ MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
+ rectDp,
+ displayLayout,
+ )
+
+ val expectedBoundsPx = Rect(100, 1300, 400, 1500)
+ assertEquals(expectedBoundsPx, actualBoundsPx)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt
new file mode 100644
index 000000000000..c8bebf11a82c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.content.res.Resources
+import android.graphics.RectF
+import android.util.DisplayMetrics
+import android.view.DisplayInfo
+import org.mockito.Mockito.spy
+
+/** Utility class for tests of [DesktopModeWindowDecorViewModel] */
+object MultiDisplayTestUtil {
+ // We have two displays, display#1 is placed on middle top of display#0:
+ // +---+
+ // | 1 |
+ // +-+---+-+
+ // | 0 |
+ // +-------+
+ val DISPLAY_GLOBAL_BOUNDS_0 = RectF(0f, 0f, 1200f, 800f)
+ val DISPLAY_GLOBAL_BOUNDS_1 = RectF(100f, -1000f, 1100f, 0f)
+ val DISPLAY_DPI_0 = DisplayMetrics.DENSITY_DEFAULT
+ val DISPLAY_DPI_1 = DisplayMetrics.DENSITY_DEFAULT * 2
+
+ fun createSpyDisplayLayout(globalBounds: RectF, dpi: Int, resources: Resources): DisplayLayout {
+ val displayInfo = DisplayInfo()
+ displayInfo.logicalDensityDpi = dpi
+ val displayLayout = spy(DisplayLayout(displayInfo, resources, true, true))
+ displayLayout.setGlobalBoundsDp(globalBounds)
+ return displayLayout
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 2b986d184c20..4b749d1274b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -257,6 +257,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val SECONDARY_DISPLAY_ID = 1
private val DISPLAY_DIMENSION_SHORT = 1600
private val DISPLAY_DIMENSION_LONG = 2560
private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275)
@@ -316,6 +317,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECONDARY_DISPLAY_ID))
+ .thenReturn(tda)
whenever(
mMockDesktopImmersiveController.exitImmersiveIfApplicable(
any(),
@@ -3588,6 +3591,45 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparent() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+
+ val currentDragBounds = Rect(100, 200, 500, 1000)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ whenever(motionEvent.displayId).thenReturn(SECONDARY_DISPLAY_ID)
+
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ position = Point(100, 200),
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ desktopWindowDecoration,
+ )
+
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.hierarchyOps[0].isReparent
+ },
+ eq(null),
+ )
+ }
+
+ @Test
fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
val spyController = spy(controller)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index f179cac32244..2207c705d7dc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -17,14 +17,14 @@ package com.android.wm.shell.windowdecor
import android.app.ActivityManager
import android.app.WindowConfiguration
-import android.content.Context
-import android.content.res.Resources
+import android.content.res.Configuration
import android.graphics.Point
import android.graphics.Rect
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.testing.AndroidTestingRunner
+import android.testing.TestableResources
import android.view.Display
import android.view.Surface.ROTATION_0
import android.view.Surface.ROTATION_270
@@ -41,6 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayTestUtil
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
@@ -55,6 +56,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -82,7 +84,6 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Mock private lateinit var taskBinder: IBinder
@Mock private lateinit var mockDisplayController: DisplayController
- @Mock private lateinit var mockDisplayLayout: DisplayLayout
@Mock private lateinit var mockDisplay: Display
@Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
@Mock private lateinit var mockTransaction: SurfaceControl.Transaction
@@ -90,9 +91,11 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Mock private lateinit var mockTransitionInfo: TransitionInfo
@Mock private lateinit var mockFinishCallback: TransitionFinishCallback
@Mock private lateinit var mockTransitions: Transitions
- @Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockResources: Resources
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ private lateinit var resources: TestableResources
+ private lateinit var spyDisplayLayout0: DisplayLayout
+ private lateinit var spyDisplayLayout1: DisplayLayout
+
private val mainHandler = Handler(Looper.getMainLooper())
private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner
@@ -101,24 +104,45 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- mockDesktopWindowDecoration.mDisplay = mockDisplay
- mockDesktopWindowDecoration.mDecorWindowContext = mockContext
- whenever(mockContext.getResources()).thenReturn(mockResources)
whenever(taskToken.asBinder()).thenReturn(taskBinder)
- whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
- whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- if (
- mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
- .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ mockDesktopWindowDecoration.mDecorWindowContext = mContext
+ resources = mContext.orCreateTestableResources
+ val resourceConfiguration = Configuration()
+ resourceConfiguration.uiMode = 0
+ resources.overrideConfiguration(resourceConfiguration)
+ spyDisplayLayout0 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0,
+ MultiDisplayTestUtil.DISPLAY_DPI_0,
+ resources.resources,
+ )
+ spyDisplayLayout1 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
+ MultiDisplayTestUtil.DISPLAY_DPI_1,
+ resources.resources,
+ )
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_0)).thenReturn(spyDisplayLayout0)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_1)).thenReturn(spyDisplayLayout1)
+ whenever(spyDisplayLayout0.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(spyDisplayLayout1.densityDpi()).thenReturn(DENSITY_DPI)
+ doAnswer { i ->
+ val rect = i.getArgument<Rect>(0)
+ if (
mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
- .displayRotation == ROTATION_270
- ) {
- (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
- } else {
- (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ rect.set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ rect.set(STABLE_BOUNDS_PORTRAIT)
+ }
+ null
}
- }
+ .`when`(spyDisplayLayout0)
+ .getStableBounds(any())
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
mockDesktopWindowDecoration.mTaskInfo =
ActivityManager.RunningTaskInfo().apply {
@@ -127,14 +151,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
minWidth = MIN_WIDTH
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
- displayId = DISPLAY_ID
+ displayId = DISPLAY_ID_0
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
isResizeable = true
}
`when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockDesktopWindowDecoration.mDisplay = mockDisplay
- whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID_0 }
taskPositioner =
MultiDisplayVeiledResizeTaskPositioner(
@@ -153,14 +177,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -185,13 +209,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() + 60,
STARTING_BOUNDS.top.toFloat() + 100,
)
@@ -205,7 +229,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
val endBounds =
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() + 70,
STARTING_BOUNDS.top.toFloat() + 20,
)
@@ -221,16 +245,39 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testDragResize_movesTaskToNewDisplay() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED,
+ DISPLAY_ID_0,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(DISPLAY_ID_1, 200f, 1900f)
+
+ val rectAfterMove = Rect(200, -50, 300, 50)
+ verify(mockTransaction)
+ .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
+
+ val endBounds = taskPositioner.onDragPositioningEnd(DISPLAY_ID_1, 300f, 450f)
+ val rectAfterEnd = Rect(300, 450, 500, 650)
+
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ Assert.assertEquals(rectAfterEnd, endBounds)
+ }
+
+ @Test
fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.right.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10,
)
@@ -252,7 +299,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
)
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.right.toFloat() + 20,
STARTING_BOUNDS.top.toFloat() + 20,
)
@@ -278,20 +325,20 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Test
fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
- DISPLAY_ID,
+ DISPLAY_ID_0,
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10,
)
@@ -326,16 +373,16 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
val newX = STARTING_BOUNDS.left.toFloat() + 5
val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
- taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY)
+ taskPositioner.onDragPositioningMove(DISPLAY_ID_0, newX, newY)
- taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, newX, newY)
verify(mockShellTaskOrganizer, never())
.applyTransaction(
@@ -354,7 +401,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -375,7 +422,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -396,7 +443,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -427,7 +474,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
rectAfterDrag.right += 2000
rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
// First drag; we should fetch stable bounds.
- verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(spyDisplayLayout0, times(1)).getStableBounds(any())
verify(mockTransitions)
.startTransition(
eq(TRANSIT_CHANGE),
@@ -451,7 +498,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
)
// Display did not rotate; we should use previous stable bounds
- verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(spyDisplayLayout0, times(1)).getStableBounds(any())
// Rotate the screen to portrait
mockDesktopWindowDecoration.mTaskInfo.apply {
@@ -482,7 +529,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
eq(taskPositioner),
)
// Display has rotated; we expect a new stable bounds.
- verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ verify(spyDisplayLayout0, times(2)).getStableBounds(any())
}
@Test
@@ -491,13 +538,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() - 20,
STARTING_BOUNDS.top.toFloat() - 20,
)
@@ -507,7 +554,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -568,10 +615,10 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) {
- taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY)
- taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY)
+ taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID_0, startX, startY)
+ taskPositioner.onDragPositioningMove(DISPLAY_ID_0, endX, endY)
- taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, endX, endY)
}
companion object {
@@ -580,7 +627,8 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
private const val MIN_HEIGHT = 10
private const val DENSITY_DPI = 20
private const val DEFAULT_MIN = 40
- private const val DISPLAY_ID = 1
+ private const val DISPLAY_ID_0 = 0
+ private const val DISPLAY_ID_1 = 1
private const val NAVBAR_HEIGHT = 50
private const val CAPTION_HEIGHT = 50
private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index aeb028ccd0a6..b7269256a449 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -18,6 +18,7 @@ package android.media.quality;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -35,6 +36,8 @@ import androidx.annotation.RequiresPermission;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -74,6 +77,39 @@ public final class MediaQualityManager {
*/
public static final String OPTION_INCLUDE_PARAMETERS = "include_parameters";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
+ AMBIENT_BACKLIGHT_EVENT_METADATA,
+ AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
+ public @interface AmbientBacklightEventTypes {}
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight is enabled.
+ * @hide
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight is disabled.
+ * @hide
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight metadata is
+ * available.
+ * @hide
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight event is
+ * preempted by another application.
+ * @hide
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4;
+
/**
* @hide
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b0309a8fa5a5..6681c014f2e0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -454,5 +454,6 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index cbdb36fff98c..9aad5d5f8367 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -689,6 +689,7 @@ public class SettingsBackupTest {
Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD,
Settings.Secure.DEVICE_PAIRED,
Settings.Secure.DIALER_DEFAULT_APPLICATION,
+ Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK,
Settings.Secure.DISABLED_PRINT_SERVICES,
Settings.Secure.DISABLE_SECURE_WINDOWS,
Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2d68ab8ff451..9531bc38e797 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1930,3 +1930,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "magnetic_notification_horizontal_swipe"
+ namespace: "systemui"
+ description: "Add support for magnetic behavior on horizontal notification swipes."
+ bug: "390179908"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index cc2a427d7939..70a74f064563 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1451,7 +1451,6 @@ private fun WidgetContent(
} else {
Modifier
}
-
Box(
modifier =
modifier
@@ -1539,7 +1538,10 @@ private fun WidgetContent(
with(widgetSection) {
Widget(
isFocusable = isFocusable,
- openWidgetEditor = { viewModel.onOpenWidgetEditor() },
+ openWidgetEditor = {
+ viewModel.setSelectedKey(model.key)
+ viewModel.onOpenWidgetEditor()
+ },
model = model,
size = size,
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
@@ -1788,6 +1790,7 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl
CustomAccessibilityAction(
context.getString(R.string.accessibility_action_label_edit_widgets)
) {
+ viewModel.setSelectedKey(null)
viewModel.onOpenWidgetEditor()
true
},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 47cba0723804..030233625027 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -367,8 +367,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
DeviceEntryRestrictionReason.AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- DeviceEntryRestrictionReason.BouncerLockedOut,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
DeviceEntryRestrictionReason.SecurityTimeout,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
@@ -403,8 +401,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
DeviceEntryRestrictionReason.AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- DeviceEntryRestrictionReason.BouncerLockedOut,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
DeviceEntryRestrictionReason.SecurityTimeout,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
@@ -440,8 +436,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
DeviceEntryRestrictionReason.AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- DeviceEntryRestrictionReason.BouncerLockedOut,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
DeviceEntryRestrictionReason.SecurityTimeout,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml
new file mode 100644
index 000000000000..5f9d4212e440
--- /dev/null
+++ b/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:width="24dp"
+ android:autoMirrored="true"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M240,800q-33,0 -56.5,-23.5T160,720q0,-33 23.5,-56.5T240,640q33,0 56.5,23.5T320,720q0,33 -23.5,56.5T240,800ZM480,800q-33,0 -56.5,-23.5T400,720q0,-33 23.5,-56.5T480,640q33,0 56.5,23.5T560,720q0,33 -23.5,56.5T480,800ZM720,800q-33,0 -56.5,-23.5T640,720q0,-33 23.5,-56.5T720,640q33,0 56.5,23.5T800,720q0,33 -23.5,56.5T720,800ZM240,560q-33,0 -56.5,-23.5T160,480q0,-33 23.5,-56.5T240,400q33,0 56.5,23.5T320,480q0,33 -23.5,56.5T240,560ZM480,560q-33,0 -56.5,-23.5T400,480q0,-33 23.5,-56.5T480,400q33,0 56.5,23.5T560,480q0,33 -23.5,56.5T480,560ZM720,560q-33,0 -56.5,-23.5T640,480q0,-33 23.5,-56.5T720,400q33,0 56.5,23.5T800,480q0,33 -23.5,56.5T720,560ZM240,320q-33,0 -56.5,-23.5T160,240q0,-33 23.5,-56.5T240,160q33,0 56.5,23.5T320,240q0,33 -23.5,56.5T240,320ZM480,320q-33,0 -56.5,-23.5T400,240q0,-33 23.5,-56.5T480,160q33,0 56.5,23.5T560,240q0,33 -23.5,56.5T480,320ZM720,320q-33,0 -56.5,-23.5T640,240q0,-33 23.5,-56.5T720,160q33,0 56.5,23.5T800,240q0,33 -23.5,56.5T720,320Z"/>
+</vector>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f2c648cb3ab0..414d3f1d17d5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3954,6 +3954,8 @@
<string name="touchpad_tutorial_home_gesture_button">Go home</string>
<!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string>
+ <!-- Label for button opening tutorial for "switch apps" gesture on touchpad [CHAR LIMIT=NONE] -->
+ <string name="touchpad_tutorial_switch_apps_gesture_button">Switch apps</string>
<!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_done_button">Done</string>
<!-- Screen title after gesture was not done correctly [CHAR LIMIT=NONE] -->
@@ -3991,6 +3993,17 @@
<string name="touchpad_recent_apps_gesture_success_body">You completed the view recent apps gesture.</string>
<!-- Text shown to the user after recent gesture was not done correctly [CHAR LIMIT=NONE] -->
<string name="touchpad_recent_gesture_error_body">To view recent apps, swipe up and hold using three fingers on your touchpad</string>
+ <!-- SWITCH APPS GESTURE -->
+ <!-- Touchpad switch apps gesture action name in tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_switch_apps_gesture_action_title">Switch apps</string>
+ <!-- Touchpad switch apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_switch_apps_gesture_guidance">Swipe left or right using four fingers on your touchpad</string>
+ <!-- Screen title after switch apps gesture was done successfully [CHAR LIMIT=NONE] -->
+ <string name="touchpad_switch_apps_gesture_success_title">Great job!</string>
+ <!-- Text shown to the user after they complete switch apps gesture tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_switch_apps_gesture_success_body">You completed the switch apps gesture.</string>
+ <!-- Text shown to the user after switch gesture was not done correctly [CHAR LIMIT=NONE] -->
+ <string name="touchpad_switch_gesture_error_body">Swipe left or right using four fingers on your touchpad to switch apps</string>
<!-- KEYBOARD TUTORIAL-->
<!-- Action key tutorial title [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 6635d8b06a5d..a061d38d0c18 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -215,7 +215,8 @@ constructor(
override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
refreshingFlow(
- initialValue = false,
+ initialValue =
+ lockPatternUtils.isAutoPinConfirmEnabled(userRepository.getSelectedUserInfo().id),
getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 7d684cab39f7..5e3b2ae0b59f 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -132,8 +132,6 @@ constructor(
DeviceEntryRestrictionReason.UnattendedUpdate
authFlags.isPrimaryAuthRequiredAfterTimeout ->
DeviceEntryRestrictionReason.SecurityTimeout
- authFlags.isPrimaryAuthRequiredAfterLockout ->
- DeviceEntryRestrictionReason.BouncerLockedOut
isFingerprintLockedOut ->
DeviceEntryRestrictionReason.StrongBiometricsLockedOut
isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
@@ -376,8 +374,7 @@ constructor(
private val interactor: DeviceUnlockedInteractor,
) : CoreStartable {
override fun start() {
- if (!SceneContainerFlag.isEnabled)
- return
+ if (!SceneContainerFlag.isEnabled) return
applicationScope.launch { interactor.activate() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
index 208a17c0a220..ebe603b7428c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
@@ -2,6 +2,8 @@ set noparent
# Bug component: 78010
+include /services/core/java/com/android/server/biometrics/OWNERS
+
amiko@google.com
beverlyt@google.com
bhinegardner@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt
index cc070b66917b..d3e2560d6a21 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.dagger
import android.content.res.Resources
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.transitions.BlurConfig
@@ -34,7 +35,6 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToOccludedTransitionViewModel
import com.android.systemui.res.R
-import com.android.systemui.window.flag.WindowBlurFlag
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -56,7 +56,7 @@ interface PrimaryBouncerTransitionModule {
fun provideBlurConfig(@Main resources: Resources): BlurConfig {
val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
val maxBlurRadius =
- if (WindowBlurFlag.isEnabled) {
+ if (Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) {
resources.getDimensionPixelSize(R.dimen.max_shade_window_blur_radius)
} else {
resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index 92bb5e6029cb..733d7d71061e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -30,7 +30,6 @@ import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
-import com.android.systemui.window.flag.WindowBlurFlag
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -73,7 +72,7 @@ constructor(
onStep = alphaForAnimationStep,
)
- val lockscreenAlpha: Flow<Float> = if (WindowBlurFlag.isEnabled) alphaFlow else emptyFlow()
+ val lockscreenAlpha: Flow<Float> = if (Flags.bouncerUiRevamp()) alphaFlow else emptyFlow()
val notificationAlpha: Flow<Float> =
if (Flags.bouncerUiRevamp()) {
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index 0650f8606ba9..9a1ffcbab8d1 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -35,7 +35,6 @@ import android.view.animation.DecelerateInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.window.flag.WindowBlurFlag;
/**
* Drawable used on SysUI scrims.
@@ -214,10 +213,6 @@ public class ScrimDrawable extends Drawable {
public void draw(@NonNull Canvas canvas) {
mPaint.setColor(mMainColor);
mPaint.setAlpha(mAlpha);
- if (WindowBlurFlag.isEnabled()) {
- // TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition
- mPaint.setAlpha((int) (0.5f * mAlpha));
- }
if (mConcaveInfo != null) {
drawConcave(canvas);
} else if (mCornerRadiusEnabled && mCornerRadius > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index 03a8d17847f9..49f3cfc4ceaf 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -39,10 +39,8 @@ import androidx.core.graphics.ColorUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.res.R;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
-import com.android.systemui.window.flag.WindowBlurFlag;
import java.util.concurrent.Executor;
@@ -252,13 +250,6 @@ public class ScrimView extends View {
if (mBlendWithMainColor) {
mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount);
}
- if (WindowBlurFlag.isEnabled()) {
- int layerAbove = ColorUtils.setAlphaComponent(
- getResources().getColor(R.color.shade_panel, null),
- (int) (0.4f * 255));
- int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255));
- mainTinted = ColorUtils.compositeColors(layerAbove, layerBelow);
- }
drawable.setColor(mainTinted, animated);
} else {
boolean hasAlpha = Color.alpha(mTintColor) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 38f7c39203f0..ca2fbdd1cdd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -34,6 +34,7 @@ import androidx.dynamicanimation.animation.SpringForce
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.TrackTracer
import com.android.systemui.Dumpable
+import com.android.systemui.Flags
import com.android.systemui.Flags.spatialModelAppPushback
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
@@ -52,8 +53,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.WallpaperController
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
-import com.android.systemui.window.flag.WindowBlurFlag
import com.android.wm.shell.appzoomout.AppZoomOut
+
import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
@@ -230,7 +231,7 @@ constructor(
val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius)
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
- if (!WindowBlurFlag.isEnabled) {
+ if (!Flags.notificationShadeBlur()) {
blur = 0
}
}
@@ -258,7 +259,9 @@ constructor(
}
private val shouldBlurBeOpaque: Boolean
- get() = if (WindowBlurFlag.isEnabled) false else scrimsVisible && !blursDisabledForAppLaunch
+ get() =
+ if (Flags.notificationShadeBlur()) false
+ else scrimsVisible && !blursDisabledForAppLaunch
/** Callback that updates the window blur value and is called only once per frame. */
@VisibleForTesting
@@ -388,7 +391,7 @@ constructor(
}
private fun initBlurListeners() {
- if (!WindowBlurFlag.isEnabled) return
+ if (!Flags.bouncerUiRevamp()) return
applicationScope.launch {
Log.d(TAG, "Starting coroutines for window root view blur")
@@ -523,7 +526,7 @@ constructor(
private fun scheduleUpdate() {
val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut()
zoomOutCalculatedFromShadeRadius = zoomOutFromShadeRadius
- if (WindowBlurFlag.isEnabled) {
+ if (Flags.bouncerUiRevamp()) {
updateScheduled =
windowRootViewBlurInteractor.requestBlurForShade(blur, shouldBlurBeOpaque)
return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index 72b03bfa20c3..b2764e1a2302 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -16,6 +16,12 @@ per-file *Keyguard* = set noparent
per-file *Keyguard* = file:../keyguard/OWNERS
# Not setting noparent here, since *Notification* also matches some status bar notification chips files (statusbar/chips/notification) which should be owned by the status bar team.
per-file *Notification* = file:notification/OWNERS
+# Files that control blur effects on shade
+per-file *NotificationShadeDepth* = set noparent
+per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com
+per-file *NotificationShadeDepth* = file:../keyguard/OWNERS
+per-file *Blur* = set noparent
+per-file *Blur* = shanh@google.com, rahulbanerjee@google.com
# Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*)
per-file *Mode* = file:notification/OWNERS
per-file *RemoteInput* = set noparent
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
index c43f31beb5bc..a2125c8f0955 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
@@ -39,6 +39,8 @@ import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureRecognizerProvider
import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureRecognizerProvider
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -69,6 +71,14 @@ interface TouchpadTutorialModule {
}
@Provides
+ fun switchAppsViewModel(
+ recognizerProvider: SwitchAppsGestureRecognizerProvider,
+ adapterFactory: GestureRecognizerAdapter.Factory,
+ ): SwitchAppsGestureScreenViewModel {
+ return SwitchAppsGestureScreenViewModel(adapterFactory.create(recognizerProvider))
+ }
+
+ @Provides
fun recentAppsViewModel(
recognizerProvider: RecentAppsGestureRecognizerProvider,
adapterFactory: GestureRecognizerAdapter.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt
new file mode 100644
index 000000000000..3bb0dd779613
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.touchpad.tutorial.ui.composable
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
+import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel
+
+@Composable
+fun SwitchAppsGestureTutorialScreen(
+ viewModel: SwitchAppsGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
+ onDoneButtonClicked: () -> Unit,
+ onBack: () -> Unit,
+) {
+ val screenConfig =
+ TutorialScreenConfig(
+ colors = rememberScreenColors(),
+ strings =
+ TutorialScreenConfig.Strings(
+ titleResId = R.string.touchpad_switch_apps_gesture_action_title,
+ bodyResId = R.string.touchpad_switch_apps_gesture_guidance,
+ titleSuccessResId = R.string.touchpad_switch_apps_gesture_success_title,
+ bodySuccessResId = R.string.touchpad_switch_apps_gesture_success_body,
+ titleErrorResId = R.string.gesture_error_title,
+ bodyErrorResId = R.string.touchpad_switch_gesture_error_body,
+ ),
+ // TODO: replace animation
+ animations = TutorialScreenConfig.Animations(educationResId = R.raw.trackpad_back_edu),
+ )
+ GestureTutorialScreen(
+ screenConfig = screenConfig,
+ tutorialStateFlow = viewModel.tutorialState,
+ motionEventConsumer = {
+ easterEggGestureViewModel.accept(it)
+ viewModel.handleEvent(it)
+ },
+ easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered,
+ onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished,
+ onDoneButtonClicked = onDoneButtonClicked,
+ onBack = onBack,
+ )
+}
+
+@Composable
+private fun rememberScreenColors(): TutorialScreenConfig.Colors {
+ val onTertiary = MaterialTheme.colorScheme.onTertiary
+ val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
+ val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
+ val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
+ val dynamicProperties =
+ rememberLottieDynamicProperties(
+ rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
+ rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed),
+ rememberColorFilterProperty(".onTertiary", onTertiary),
+ rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant),
+ )
+ val screenColors =
+ remember(dynamicProperties) {
+ TutorialScreenConfig.Colors(
+ background = onTertiaryFixed,
+ title = tertiaryFixedDim,
+ animationColors = dynamicProperties,
+ )
+ }
+ return screenColors
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index c8a58400069e..69b7e892a380 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -61,6 +61,7 @@ fun TutorialSelectionScreen(
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
onDoneButtonClicked: () -> Unit,
lastSelectedScreen: Screen,
) {
@@ -86,6 +87,7 @@ fun TutorialSelectionScreen(
onBackTutorialClicked = onBackTutorialClicked,
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).padding(60.dp),
lastSelectedScreen,
)
@@ -95,6 +97,7 @@ fun TutorialSelectionScreen(
onBackTutorialClicked = onBackTutorialClicked,
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).padding(60.dp),
lastSelectedScreen,
)
@@ -113,6 +116,7 @@ private fun HorizontalSelectionButtons(
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
lastSelectedScreen: Screen,
) {
@@ -121,10 +125,11 @@ private fun HorizontalSelectionButtons(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
- ThreeTutorialButtons(
+ FourTutorialButtons(
onBackTutorialClicked,
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).fillMaxSize(),
lastSelectedScreen,
)
@@ -136,6 +141,7 @@ private fun VerticalSelectionButtons(
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
lastSelectedScreen: Screen,
) {
@@ -144,10 +150,11 @@ private fun VerticalSelectionButtons(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier,
) {
- ThreeTutorialButtons(
+ FourTutorialButtons(
onBackTutorialClicked,
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).fillMaxSize(),
lastSelectedScreen,
)
@@ -155,21 +162,24 @@ private fun VerticalSelectionButtons(
}
@Composable
-private fun ThreeTutorialButtons(
+private fun FourTutorialButtons(
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
lastSelectedScreen: Screen,
) {
val homeFocusRequester = remember { FocusRequester() }
val backFocusRequester = remember { FocusRequester() }
val recentAppsFocusRequester = remember { FocusRequester() }
+ val switchAppsFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
when (lastSelectedScreen) {
Screen.HOME_GESTURE -> homeFocusRequester.requestFocus()
Screen.BACK_GESTURE -> backFocusRequester.requestFocus()
Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus()
+ Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus()
else -> {} // No-Op.
}
}
@@ -197,6 +207,14 @@ private fun ThreeTutorialButtons(
backgroundColor = MaterialTheme.colorScheme.secondary,
modifier = modifier.focusRequester(recentAppsFocusRequester).focusable(),
)
+ TutorialButton(
+ text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon),
+ iconColor = MaterialTheme.colorScheme.primary,
+ onClick = onSwitchAppsTutorialClicked,
+ backgroundColor = MaterialTheme.colorScheme.onPrimary,
+ modifier = modifier.focusRequester(switchAppsFocusRequester).focusable(),
+ )
}
@Composable
@@ -227,7 +245,7 @@ private fun TutorialButton(
tint = iconColor,
)
Spacer(modifier = Modifier.height(16.dp))
- Text(text = text, style = MaterialTheme.typography.headlineLarge)
+ Text(text = text, style = MaterialTheme.typography.headlineLarge, color = iconColor)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt
new file mode 100644
index 000000000000..470048bd3b20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt
@@ -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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+
+// TODO: javadoc
+class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer {
+
+ private val distanceTracker = DistanceTracker()
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
+
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
+
+ override fun clearGestureStateCallback() {
+ gestureStateChangedCallback = {}
+ }
+
+ // TODO: recognizer logic
+ override fun accept(event: MotionEvent) {
+ if (!isMultifingerTouchpadSwipe(event)) return
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 3264300ed908..0a139125afa2 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -37,6 +37,7 @@ import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
+import com.android.systemui.touchpad.tutorial.ui.composable.SwitchAppsGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
@@ -45,7 +46,9 @@ import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScre
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.RECENT_APPS_GESTURE
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.SWITCH_APPS_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.TouchpadTutorialViewModel
import javax.inject.Inject
@@ -58,6 +61,7 @@ constructor(
private val backGestureViewModel: BackGestureScreenViewModel,
private val homeGestureViewModel: HomeGestureScreenViewModel,
private val recentAppsGestureViewModel: RecentAppsGestureScreenViewModel,
+ private val switchAppsGestureScreenViewModel: SwitchAppsGestureScreenViewModel,
private val easterEggGestureViewModel: EasterEggGestureViewModel,
) : ComponentActivity() {
@@ -75,6 +79,7 @@ constructor(
backGestureViewModel,
homeGestureViewModel,
recentAppsGestureViewModel,
+ switchAppsGestureScreenViewModel,
easterEggGestureViewModel,
closeTutorial = ::finishTutorial,
)
@@ -108,6 +113,7 @@ fun TouchpadTutorialScreen(
backGestureViewModel: BackGestureScreenViewModel,
homeGestureViewModel: HomeGestureScreenViewModel,
recentAppsGestureViewModel: RecentAppsGestureScreenViewModel,
+ switchAppsGestureScreenViewModel: SwitchAppsGestureScreenViewModel,
easterEggGestureViewModel: EasterEggGestureViewModel,
closeTutorial: () -> Unit,
) {
@@ -128,6 +134,10 @@ fun TouchpadTutorialScreen(
lastSelectedScreen = RECENT_APPS_GESTURE
vm.goTo(RECENT_APPS_GESTURE)
},
+ onSwitchAppsTutorialClicked = {
+ lastSelectedScreen = SWITCH_APPS_GESTURE
+ vm.goTo(SWITCH_APPS_GESTURE)
+ },
onDoneButtonClicked = closeTutorial,
lastSelectedScreen,
)
@@ -152,5 +162,12 @@ fun TouchpadTutorialScreen(
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
+ SWITCH_APPS_GESTURE ->
+ SwitchAppsGestureTutorialScreen(
+ switchAppsGestureScreenViewModel,
+ easterEggGestureViewModel,
+ onDoneButtonClicked = { vm.goTo(SWITCH_APPS_GESTURE) },
+ onBack = { vm.goTo(TUTORIAL_SELECTION) },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt
new file mode 100644
index 000000000000..b1e163b55377
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.touchpad.tutorial.ui.viewmodel
+
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
+import com.android.systemui.touchpad.tutorial.ui.gesture.SwitchAppsGestureRecognizer
+import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class SwitchAppsGestureRecognizerProvider
+@Inject
+constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) :
+ GestureRecognizerProvider {
+
+ override val recognizer: Flow<GestureRecognizer> =
+ resources.distanceThreshold().map {
+ SwitchAppsGestureRecognizer(gestureDistanceThresholdPx = it)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt
new file mode 100644
index 000000000000..6593db49745d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.touchpad.tutorial.ui.viewmodel
+
+import android.view.MotionEvent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class SwitchAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) :
+ TouchpadTutorialScreenViewModel {
+
+ // TODO: replace with correct markers and resource
+ override val tutorialState: Flow<TutorialActionState> =
+ gestureRecognizer.gestureState
+ .map {
+ it to
+ TutorialAnimationProperties(
+ progressStartMarker = "drag with gesture",
+ progressEndMarker = "onPause",
+ successAnimation = R.raw.trackpad_recent_apps_success,
+ )
+ }
+ .mapToTutorialState()
+
+ override fun handleEvent(event: MotionEvent): Boolean {
+ return gestureRecognizer.handleTouchpadMotionEvent(event)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
index c56dcf3bf062..c6d5e7ad0a71 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
@@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.StateFlow
class TouchpadTutorialViewModel(
private val gesturesInteractor: TouchpadGesturesInteractor,
- private val logger: InputDeviceTutorialLogger
+ private val logger: InputDeviceTutorialLogger,
) : ViewModel() {
private val _screen = MutableStateFlow(Screen.TUTORIAL_SELECTION)
@@ -50,7 +50,7 @@ class TouchpadTutorialViewModel(
@Inject
constructor(
private val gesturesInteractor: TouchpadGesturesInteractor,
- private val logger: InputDeviceTutorialLogger
+ private val logger: InputDeviceTutorialLogger,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -65,4 +65,5 @@ enum class Screen {
BACK_GESTURE,
HOME_GESTURE,
RECENT_APPS_GESTURE,
+ SWITCH_APPS_GESTURE,
}
diff --git a/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt b/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt
deleted file mode 100644
index 8b6c8601f5d2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.window.flag
-
-import com.android.systemui.Flags
-
-/**
- * Flag that controls whether the background surface is blurred or not while on the
- * lockscreen/shade/bouncer. This makes the background of scrim, bouncer and few other opaque
- * surfaces transparent so that we can see the blur effect on the background surface (wallpaper).
- */
-object WindowBlurFlag {
- /** Whether the blur is enabled or not */
- @JvmStatic
- val isEnabled
- // Add flags here that require scrims/background surfaces to be transparent.
- get() = Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt
index 2491ca7565c7..d2069cfdfdc6 100644
--- a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt
@@ -19,12 +19,12 @@ package com.android.systemui.window.ui
import android.util.Log
import android.view.Choreographer
import android.view.Choreographer.FrameCallback
+import com.android.systemui.Flags
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.statusbar.BlurUtils
-import com.android.systemui.window.flag.WindowBlurFlag
import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.filter
@@ -43,7 +43,7 @@ object WindowRootViewBinder {
blurUtils: BlurUtils?,
choreographer: Choreographer?,
) {
- if (!WindowBlurFlag.isEnabled) return
+ if (!Flags.bouncerUiRevamp()) return
if (blurUtils == null || choreographer == null) return
view.repeatWhenAttached {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 79888b051c54..70c4c1311fc9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -21,14 +21,14 @@ import static android.view.MotionEvent.ACTION_SCROLL;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+
import android.accessibilityservice.AccessibilityTrace;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
import android.hardware.input.InputManager;
-import android.hardware.input.KeyGestureEvent;
-import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -46,15 +46,13 @@ import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.accessibility.AccessibilityEvent;
-import androidx.annotation.Nullable;
-
import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
-import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
+import com.android.server.accessibility.magnification.MagnificationKeyHandler;
import com.android.server.accessibility.magnification.MouseEventHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
@@ -209,6 +207,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private MouseKeysInterceptor mMouseKeysInterceptor;
+ private MagnificationKeyHandler mMagnificationKeyHandler;
+
private boolean mInstalled;
private int mUserId;
@@ -235,74 +235,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
private MotionEvent mLastActiveDeviceMotionEvent = null;
- private boolean mKeyGestureEventHandlerInstalled = false;
- private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
- new InputManager.KeyGestureEventHandler() {
- @Override
- public boolean handleKeyGestureEvent(
- @NonNull KeyGestureEvent event,
- @Nullable IBinder focusedToken) {
- final boolean complete =
- event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
- && !event.isCancelled();
-
- // TODO(b/355499907): Receive and handle held key gestures, which can be used
- // for continuous scaling and panning. In addition, handle multiple pan gestures
- // at the same time (e.g. user may try to pan diagonally) reasonably, including
- // decreasing diagonal movement by sqrt(2) to make it appear the same speed
- // as non-diagonal movement.
-
- if (!complete) {
- return false;
- }
-
- final int gestureType = event.getKeyGestureType();
- final int displayId = isDisplayIdValid(event.getDisplayId())
- ? event.getDisplayId() : Display.DEFAULT_DISPLAY;
-
- switch (gestureType) {
- case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN:
- mAms.getMagnificationController().scaleMagnificationByStep(
- displayId, MagnificationController.ZOOM_DIRECTION_IN);
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT:
- mAms.getMagnificationController().scaleMagnificationByStep(
- displayId, MagnificationController.ZOOM_DIRECTION_OUT);
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT:
- mAms.getMagnificationController().panMagnificationByStep(
- displayId, MagnificationController.PAN_DIRECTION_LEFT);
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT:
- mAms.getMagnificationController().panMagnificationByStep(
- displayId, MagnificationController.PAN_DIRECTION_RIGHT);
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP:
- mAms.getMagnificationController().panMagnificationByStep(
- displayId, MagnificationController.PAN_DIRECTION_UP);
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN:
- mAms.getMagnificationController().panMagnificationByStep(
- displayId, MagnificationController.PAN_DIRECTION_DOWN);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean isKeyGestureSupported(int gestureType) {
- return switch (gestureType) {
- case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN -> true;
- default -> false;
- };
- }
- };
-
private static MotionEvent cancelMotion(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
|| event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT
@@ -787,20 +719,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
});
}
- if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
- || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0)
- || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0)
- || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
+ if (isAnyMagnificationEnabled()) {
final MagnificationGestureHandler magnificationGestureHandler =
createMagnificationGestureHandler(displayId, displayContext);
addFirstEventHandler(displayId, magnificationGestureHandler);
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
-
- if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()
- && !mKeyGestureEventHandlerInstalled) {
- mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
- mKeyGestureEventHandlerInstalled = true;
- }
}
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
@@ -817,6 +740,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
+ // mKeyboardInterceptor does not forward KeyEvents to other EventStreamTransformations,
+ // so it must be the last EventStreamTransformation for key events in the list.
mKeyboardInterceptor = new KeyboardInterceptor(mAms,
LocalServices.getService(WindowManagerPolicy.class));
// Since the display id of KeyEvent always would be -1 and it would be dispatched to
@@ -832,6 +757,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
Display.DEFAULT_DISPLAY);
addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
}
+
+ if (enableTalkbackAndMagnifierKeyGestures() && isAnyMagnificationEnabled()) {
+ mMagnificationKeyHandler = new MagnificationKeyHandler(
+ mAms.getMagnificationController());
+ addFirstEventHandler(Display.DEFAULT_DISPLAY, mMagnificationKeyHandler);
+ }
+ }
+
+ private boolean isAnyMagnificationEnabled() {
+ return (mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
+ || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0)
+ || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0)
+ || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0);
}
/**
@@ -921,9 +859,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mMouseKeysInterceptor = null;
}
- if (mKeyGestureEventHandlerInstalled) {
- mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler);
- mKeyGestureEventHandlerInstalled = false;
+ if (mMagnificationKeyHandler != null) {
+ mMagnificationKeyHandler.onDestroy();
+ mMagnificationKeyHandler = null;
}
}
@@ -1365,6 +1303,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
joiner.add("AutoclickController");
} else if (next instanceof MotionEventInjector) {
joiner.add("MotionEventInjector");
+ } else if (next instanceof MagnificationKeyHandler) {
+ joiner.add("MagnificationKeyHandler");
}
next = next.getNext();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 2e131b696afc..75ec8ea88ace 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -84,7 +84,7 @@ import java.util.concurrent.Executor;
* is done and before invoking {@link TransitionCallBack#onResult}.
*/
public class MagnificationController implements MagnificationConnectionManager.Callback,
- MagnificationGestureHandler.Callback,
+ MagnificationGestureHandler.Callback, MagnificationKeyHandler.Callback,
FullScreenMagnificationController.MagnificationInfoChangedCallback,
WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
@@ -347,6 +347,36 @@ public class MagnificationController implements MagnificationConnectionManager.C
handleUserInteractionChanged(displayId, mode);
}
+ @Override
+ public void onPanMagnificationStart(int displayId,
+ @MagnificationController.PanDirection int direction) {
+ // TODO(b/355499907): Handle multiple pan gestures at the same time (e.g. user may try to
+ // pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same
+ // speed as non-diagonal movement.
+ panMagnificationByStep(displayId, direction);
+ }
+
+ @Override
+ public void onPanMagnificationStop(int displayId,
+ @MagnificationController.PanDirection int direction) {
+ // TODO(b/388847283): Handle held key gestures, which can be used
+ // for continuous scaling and panning, until they are released.
+
+ }
+
+ @Override
+ public void onScaleMagnificationStart(int displayId,
+ @MagnificationController.ZoomDirection int direction) {
+ scaleMagnificationByStep(displayId, direction);
+ }
+
+ @Override
+ public void onScaleMagnificationStop(int displayId,
+ @MagnificationController.ZoomDirection int direction) {
+ // TODO(b/388847283): Handle held key gestures, which can be used
+ // for continuous scaling and panning, until they are released.
+ }
+
private void handleUserInteractionChanged(int displayId, int mode) {
if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
return;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java
new file mode 100644
index 000000000000..a65580c82124
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java
@@ -0,0 +1,133 @@
+/*
+ * 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.server.accessibility.magnification;
+
+import android.view.Display;
+import android.view.KeyEvent;
+
+import com.android.server.accessibility.BaseEventStreamTransformation;
+
+/*
+ * A class that listens to key presses used to control magnification.
+ */
+public class MagnificationKeyHandler extends BaseEventStreamTransformation {
+
+ /** Callback interface to report that a user is intending to interact with Magnification. */
+ public interface Callback {
+ /**
+ * Called when a keyboard shortcut to pan magnification in direction {@code direction} is
+ * pressed by a user. Note that this can be called for multiple directions if multiple
+ * arrows are pressed at the same time (e.g. diagonal panning).
+ *
+ * @param displayId The logical display ID
+ * @param direction The direction to start panning
+ */
+ void onPanMagnificationStart(int displayId,
+ @MagnificationController.PanDirection int direction);
+
+ /**
+ * Called when a keyboard shortcut to pan magnification in direction {@code direction} is
+ * unpressed by a user. Note that this can be called for multiple directions if multiple
+ * arrows had been pressed at the same time (e.g. diagonal panning).
+ *
+ * @param displayId The logical display ID
+ * @param direction The direction in which panning stopped
+ */
+ void onPanMagnificationStop(int displayId,
+ @MagnificationController.PanDirection int direction);
+
+ /**
+ * Called when a keyboard shortcut to scale magnification in direction `direction` is
+ * pressed by a user.
+ *
+ * @param displayId The logical display ID
+ * @param direction The direction in which scaling started
+ */
+ void onScaleMagnificationStart(int displayId,
+ @MagnificationController.ZoomDirection int direction);
+
+ /**
+ * Called when a keyboard shortcut to scale magnification in direction `direction` is
+ * unpressed by a user.
+ *
+ * @param displayId The logical display ID
+ * @param direction The direction in which scaling stopped
+ */
+ void onScaleMagnificationStop(int displayId,
+ @MagnificationController.ZoomDirection int direction);
+ }
+
+ protected final MagnificationKeyHandler.Callback mCallback;
+
+ public MagnificationKeyHandler(Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (!com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()) {
+ // Send to the rest of the handlers.
+ super.onKeyEvent(event, policyFlags);
+ return;
+ }
+ boolean modifiersPressed = event.isAltPressed() && event.isMetaPressed();
+ if (!modifiersPressed) {
+ super.onKeyEvent(event, policyFlags);
+ return;
+ }
+ boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
+ int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+ || keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ int panDirection = switch(keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT -> MagnificationController.PAN_DIRECTION_LEFT;
+ case KeyEvent.KEYCODE_DPAD_RIGHT -> MagnificationController.PAN_DIRECTION_RIGHT;
+ case KeyEvent.KEYCODE_DPAD_UP -> MagnificationController.PAN_DIRECTION_UP;
+ default -> MagnificationController.PAN_DIRECTION_DOWN;
+ };
+ if (isDown) {
+ mCallback.onPanMagnificationStart(getDisplayId(event), panDirection);
+ } else {
+ mCallback.onPanMagnificationStop(getDisplayId(event), panDirection);
+ }
+ return;
+ } else if (keyCode == KeyEvent.KEYCODE_EQUALS || keyCode == KeyEvent.KEYCODE_MINUS) {
+ int zoomDirection = MagnificationController.ZOOM_DIRECTION_OUT;
+ if (keyCode == KeyEvent.KEYCODE_EQUALS) {
+ zoomDirection = MagnificationController.ZOOM_DIRECTION_IN;
+ }
+ if (isDown) {
+ mCallback.onScaleMagnificationStart(getDisplayId(event), zoomDirection);
+ } else {
+ mCallback.onScaleMagnificationStop(getDisplayId(event), zoomDirection);
+ }
+ return;
+ }
+
+ // Continue down the eventing chain if this was unused.
+ super.onKeyEvent(event, policyFlags);
+ }
+
+ private int getDisplayId(KeyEvent event) {
+ // Display ID may be invalid, e.g. for external keyboard attached to phone.
+ // In that case, use the default display.
+ if (event.getDisplayId() != Display.INVALID_DISPLAY) {
+ return event.getDisplayId();
+ }
+ return Display.DEFAULT_DISPLAY;
+ }
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d76c04ac7f31..27e9e44f1090 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -4062,8 +4062,7 @@ class UserController implements Handler.Callback {
synchronized (mUserSwitchingDialogLock) {
dismissUserSwitchingDialog(null);
mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
- switchingFromSystemUserMessage, switchingToSystemUserMessage,
- getWindowManager());
+ switchingFromSystemUserMessage, switchingToSystemUserMessage);
mUserSwitchingDialog.show(onShown);
}
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 2d7456471be4..d1fcb9d1ca37 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -39,7 +39,6 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.Slog;
import android.util.TypedValue;
import android.view.View;
@@ -53,7 +52,6 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.util.ObjectUtils;
import com.android.internal.util.UserIcons;
-import com.android.server.wm.WindowManagerService;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -80,14 +78,11 @@ class UserSwitchingDialog extends Dialog {
protected final UserInfo mNewUser;
private final String mSwitchingFromSystemUserMessage;
private final String mSwitchingToSystemUserMessage;
- private final WindowManagerService mWindowManager;
protected final Context mContext;
private final int mTraceCookie;
- private final boolean mNeedToFreezeScreen;
UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
- String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
- WindowManagerService windowManager) {
+ String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
mContext = context;
@@ -97,8 +92,6 @@ class UserSwitchingDialog extends Dialog {
mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
mDisableAnimations = SystemProperties.getBoolean(
"debug.usercontroller.disable_user_switching_dialog_animations", false);
- mWindowManager = windowManager;
- mNeedToFreezeScreen = !mDisableAnimations && !isUserSetupComplete(newUser);
mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id;
inflateContent();
@@ -183,11 +176,6 @@ class UserSwitchingDialog extends Dialog {
: res.getString(R.string.user_switching_message, mNewUser.name);
}
- private boolean isUserSetupComplete(UserInfo user) {
- return Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, /* default= */ 0, user.id) == 1;
- }
-
@Override
public void show() {
asyncTraceBegin("dialog", 0);
@@ -197,7 +185,6 @@ class UserSwitchingDialog extends Dialog {
@Override
public void dismiss() {
super.dismiss();
- stopFreezingScreen();
asyncTraceEnd("dialog", 0);
}
@@ -205,7 +192,6 @@ class UserSwitchingDialog extends Dialog {
if (DEBUG) Slog.d(TAG, "show called");
show();
startShowAnimation(() -> {
- startFreezingScreen();
onShown.run();
});
}
@@ -223,24 +209,6 @@ class UserSwitchingDialog extends Dialog {
}
}
- private void startFreezingScreen() {
- if (!mNeedToFreezeScreen) {
- return;
- }
- traceBegin("startFreezingScreen");
- mWindowManager.startFreezingScreen(0, 0);
- traceEnd("startFreezingScreen");
- }
-
- private void stopFreezingScreen() {
- if (!mNeedToFreezeScreen) {
- return;
- }
- traceBegin("stopFreezingScreen");
- mWindowManager.stopFreezingScreen();
- traceEnd("stopFreezingScreen");
- }
-
private void startShowAnimation(Runnable onAnimationEnd) {
if (mDisableAnimations) {
onAnimationEnd.run();
@@ -260,7 +228,7 @@ class UserSwitchingDialog extends Dialog {
}
private void startDismissAnimation(Runnable onAnimationEnd) {
- if (mDisableAnimations || mNeedToFreezeScreen) {
+ if (mDisableAnimations) {
// animations are disabled or screen is frozen, no need to play an animation
onAnimationEnd.run();
return;
@@ -352,14 +320,4 @@ class UserSwitchingDialog extends Dialog {
Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
if (DEBUG) Slog.d(TAG, "asyncTraceEnd-" + subTag);
}
-
- private void traceBegin(String msg) {
- if (DEBUG) Slog.d(TAG, "traceBegin-" + msg);
- Trace.traceBegin(TRACE_TAG, msg);
- }
-
- private void traceEnd(String msg) {
- Trace.traceEnd(TRACE_TAG);
- if (DEBUG) Slog.d(TAG, "traceEnd-" + msg);
- }
}
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 93d9b8d30a2e..25a2f60b85b2 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -643,8 +643,9 @@ final class ColorFade {
.setSecure(isSecure)
.setBLASTLayer();
mBLASTSurfaceControl = b.build();
- mBLASTBufferQueue = new BLASTBufferQueue("ColorFade", mBLASTSurfaceControl,
- mDisplayWidth, mDisplayHeight, PixelFormat.TRANSLUCENT);
+ mBLASTBufferQueue = new BLASTBufferQueue("ColorFade", /*updateDestinationFrame*/ true);
+ mBLASTBufferQueue.update(mBLASTSurfaceControl, mDisplayWidth, mDisplayHeight,
+ PixelFormat.TRANSLUCENT);
mSurface = mBLASTBufferQueue.createSurface();
}
return true;
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
index ff1a74af02e2..9118c46e3b22 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
@@ -61,6 +61,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction {
@VisibleForTesting
static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3;
+ // State in which we wait for device to complete a possible power state change triggered by
+ // <Set Stream Path>.
+ @VisibleForTesting
+ static final int STATE_WAIT_FOR_POWER_STATE_CHANGE = 4;
+
private final HdmiDeviceInfo mTarget;
private final HdmiCecMessage mGivePowerStatus;
private final boolean mIsCec20;
@@ -100,7 +105,12 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction {
// Wake-up on <Set Stream Path> was not mandatory before CEC 2.0.
// The message is re-sent at the end of the action for devices that don't support 2.0.
sendSetStreamPath();
+ mState = STATE_WAIT_FOR_POWER_STATE_CHANGE;
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ return true;
+ }
+ private void checkForPowerStateChange() {
if (!mIsCec20) {
queryDevicePowerStatus();
} else {
@@ -114,12 +124,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction {
queryDevicePowerStatus();
} else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
+ return;
}
}
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
- return true;
}
private void queryDevicePowerStatus() {
@@ -210,6 +219,9 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction {
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
break;
+ case STATE_WAIT_FOR_POWER_STATE_CHANGE:
+ checkForPowerStateChange();
+ break;
}
}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 93fdbc787ed0..fd755e3cefe2 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -87,7 +87,20 @@ final class InputGestureManager {
createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON),
createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON),
createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON),
- createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
+ createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON),
+ // Used for magnification viewport control.
+ createKeyTrigger(KeyEvent.KEYCODE_MINUS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_EQUALS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON)
));
public InputGestureManager(Context context) {
@@ -216,24 +229,6 @@ final class InputGestureManager {
systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T,
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
- systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT));
- systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN));
- systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_LEFT,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT));
- systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_RIGHT,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT));
- systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_UP,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP));
- systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_DOWN,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN));
systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M,
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION));
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index efc1b9959c0f..e47cbdc3546f 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -16,18 +16,33 @@
package com.android.server.media.quality;
+import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_ENABLED;
+import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_DISABLED;
+import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_METADATA_AVAILABLE;
+import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_INTERRUPTED;
+
+import android.annotation.NonNull;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.hardware.tv.mediaquality.AmbientBacklightColorFormat;
import android.hardware.tv.mediaquality.IMediaQuality;
+import android.hardware.tv.mediaquality.PictureParameter;
+import android.hardware.tv.mediaquality.PictureParameters;
+import android.hardware.tv.mediaquality.SoundParameter;
+import android.hardware.tv.mediaquality.SoundParameters;
+import android.media.quality.AmbientBacklightEvent;
+import android.media.quality.AmbientBacklightMetadata;
import android.media.quality.AmbientBacklightSettings;
import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IMediaQualityManager;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
import android.media.quality.MediaQualityContract.BaseParameters;
+import android.media.quality.MediaQualityContract.PictureQuality;
+import android.media.quality.MediaQualityContract.SoundQuality;
import android.media.quality.MediaQualityManager;
import android.media.quality.ParameterCapability;
import android.media.quality.PictureProfile;
@@ -42,6 +57,7 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -60,6 +76,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -76,13 +93,16 @@ public class MediaQualityService extends SystemService {
private final MediaQualityDbHelper mMediaQualityDbHelper;
private final BiMap<Long, String> mPictureProfileTempIdMap;
private final BiMap<Long, String> mSoundProfileTempIdMap;
+ private IMediaQuality mMediaQuality;
+ private final HalAmbientBacklightCallback mHalAmbientBacklightCallback;
+ private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>();
private final PackageManager mPackageManager;
private final SparseArray<UserState> mUserStates = new SparseArray<>();
- private IMediaQuality mMediaQuality;
public MediaQualityService(Context context) {
super(context);
mContext = context;
+ mHalAmbientBacklightCallback = new HalAmbientBacklightCallback();
mPackageManager = mContext.getPackageManager();
mPictureProfileTempIdMap = new BiMap<>();
mSoundProfileTempIdMap = new BiMap<>();
@@ -97,6 +117,13 @@ public class MediaQualityService extends SystemService {
if (binder != null) {
Slogf.d(TAG, "binder is not null");
mMediaQuality = IMediaQuality.Stub.asInterface(binder);
+ if (mMediaQuality != null) {
+ try {
+ mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set ambient backlight detector callback", e);
+ }
+ }
}
publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService());
@@ -282,10 +309,208 @@ public class MediaQualityService extends SystemService {
notifyOnPictureProfileError(profileId, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
- // TODO: pass the profile ID to MediaQuality HAL when ready.
+
+ PictureProfile pictureProfile = getPictureProfile(
+ mPictureProfileTempIdMap.getKey(profileId));
+ PersistableBundle params = pictureProfile.getParameters();
+
+ try {
+ if (mMediaQuality != null) {
+ PictureParameter[] pictureParameters =
+ convertPersistableBundleToPictureParameterList(params);
+
+ PictureParameters pp = new PictureParameters();
+ pp.pictureParameters = pictureParameters;
+
+ mMediaQuality.sendDefaultPictureParameters(pp);
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set default picture profile", e);
+ }
return false;
}
+ private PictureParameter[] convertPersistableBundleToPictureParameterList(
+ PersistableBundle params) {
+ List<PictureParameter> pictureParams = new ArrayList<>();
+ if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ pictureParams.add(PictureParameter.brightness(params.getLong(
+ PictureQuality.PARAMETER_BRIGHTNESS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_CONTRAST)) {
+ pictureParams.add(PictureParameter.contrast(params.getInt(
+ PictureQuality.PARAMETER_CONTRAST)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_SHARPNESS)) {
+ pictureParams.add(PictureParameter.sharpness(params.getInt(
+ PictureQuality.PARAMETER_SHARPNESS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_SATURATION)) {
+ pictureParams.add(PictureParameter.saturation(params.getInt(
+ PictureQuality.PARAMETER_SATURATION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_HUE)) {
+ pictureParams.add(PictureParameter.hue(params.getInt(
+ PictureQuality.PARAMETER_HUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
+ pictureParams.add(PictureParameter.colorTunerBrightness(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
+ pictureParams.add(PictureParameter.colorTunerSaturation(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
+ pictureParams.add(PictureParameter.colorTunerHue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTunerRedOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTunerGreenOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTunerBlueOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+ pictureParams.add(PictureParameter.colorTunerRedGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+ pictureParams.add(PictureParameter.colorTunerGreenGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+ pictureParams.add(PictureParameter.colorTunerBlueGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
+ pictureParams.add(PictureParameter.noiseReduction(
+ (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
+ pictureParams.add(PictureParameter.mpegNoiseReduction(
+ (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) {
+ pictureParams.add(PictureParameter.fleshTone(
+ (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) {
+ pictureParams.add(PictureParameter.deContour(
+ (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
+ pictureParams.add(PictureParameter.dynamicLumaControl(
+ (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) {
+ pictureParams.add(PictureParameter.filmMode(params.getBoolean(
+ PictureQuality.PARAMETER_FILM_MODE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_BLUE_STRETCH)) {
+ pictureParams.add(PictureParameter.blueStretch(params.getBoolean(
+ PictureQuality.PARAMETER_BLUE_STRETCH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNE)) {
+ pictureParams.add(PictureParameter.colorTune(params.getBoolean(
+ PictureQuality.PARAMETER_COLOR_TUNE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
+ pictureParams.add(PictureParameter.colorTemperature(
+ (byte) params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
+ pictureParams.add(PictureParameter.globeDimming(params.getBoolean(
+ PictureQuality.PARAMETER_GLOBAL_DIMMING)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
+ pictureParams.add(PictureParameter.autoPictureQualityEnabled(params.getBoolean(
+ PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
+ pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean(
+ PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+ pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+ pictureParams.add(PictureParameter.colorTemperatureGreenGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+ pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
+ }
+
+ /**
+ * TODO: add conversion for following after adding to MediaQualityContract
+ *
+ * PictureParameter.levelRange
+ * PictureParameter.gamutMapping
+ * PictureParameter.pcMode
+ * PictureParameter.lowLatency
+ * PictureParameter.vrr
+ * PictureParameter.cvrr
+ * PictureParameter.hdmiRgbRange
+ * PictureParameter.colorSpace
+ * PictureParameter.panelInitMaxLuminceNits
+ * PictureParameter.panelInitMaxLuminceValid
+ * PictureParameter.gamma
+ * PictureParameter.colorTemperatureRedOffset
+ * PictureParameter.colorTemperatureGreenOffset
+ * PictureParameter.colorTemperatureBlueOffset
+ * PictureParameter.elevenPointRed
+ * PictureParameter.elevenPointGreen
+ * PictureParameter.elevenPointBlue
+ * PictureParameter.lowBlueLight
+ * PictureParameter.LdMode
+ * PictureParameter.osdRedGain
+ * PictureParameter.osdGreenGain
+ * PictureParameter.osdBlueGain
+ * PictureParameter.osdRedOffset
+ * PictureParameter.osdGreenOffset
+ * PictureParameter.osdBlueOffset
+ * PictureParameter.osdHue
+ * PictureParameter.osdSaturation
+ * PictureParameter.osdContrast
+ * PictureParameter.colorTunerSwitch
+ * PictureParameter.colorTunerHueRed
+ * PictureParameter.colorTunerHueGreen
+ * PictureParameter.colorTunerHueBlue
+ * PictureParameter.colorTunerHueCyan
+ * PictureParameter.colorTunerHueMagenta
+ * PictureParameter.colorTunerHueYellow
+ * PictureParameter.colorTunerHueFlesh
+ * PictureParameter.colorTunerSaturationRed
+ * PictureParameter.colorTunerSaturationGreen
+ * PictureParameter.colorTunerSaturationBlue
+ * PictureParameter.colorTunerSaturationCyan
+ * PictureParameter.colorTunerSaturationMagenta
+ * PictureParameter.colorTunerSaturationYellow
+ * PictureParameter.colorTunerSaturationFlesh
+ * PictureParameter.colorTunerLuminanceRed
+ * PictureParameter.colorTunerLuminanceGreen
+ * PictureParameter.colorTunerLuminanceBlue
+ * PictureParameter.colorTunerLuminanceCyan
+ * PictureParameter.colorTunerLuminanceMagenta
+ * PictureParameter.colorTunerLuminanceYellow
+ * PictureParameter.colorTunerLuminanceFlesh
+ * PictureParameter.activeProfile
+ * PictureParameter.pictureQualityEventType
+ */
+ return (PictureParameter[]) pictureParams.toArray();
+ }
+
@Override
public List<String> getPictureProfilePackageNames(UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
@@ -503,10 +728,77 @@ public class MediaQualityService extends SystemService {
notifyOnSoundProfileError(profileId, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
- // TODO: pass the profile ID to MediaQuality HAL when ready.
+
+ SoundProfile soundProfile = getSoundProfile(mSoundProfileTempIdMap.getKey(profileId));
+ PersistableBundle params = soundProfile.getParameters();
+
+ try {
+ if (mMediaQuality != null) {
+ SoundParameter[] soundParameters =
+ convertPersistableBundleToSoundParameterList(params);
+
+ SoundParameters sp = new SoundParameters();
+ sp.soundParameters = soundParameters;
+
+ mMediaQuality.sendDefaultSoundParameters(sp);
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set default sound profile", e);
+ }
return false;
}
+ private SoundParameter[] convertPersistableBundleToSoundParameterList(
+ PersistableBundle params) {
+ List<SoundParameter> soundParams = new ArrayList<>();
+ if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) {
+ soundParams.add(SoundParameter.balance(params.getInt(
+ SoundQuality.PARAMETER_BALANCE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_BASS)) {
+ soundParams.add(SoundParameter.bass(params.getInt(SoundQuality.PARAMETER_BASS)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_TREBLE)) {
+ soundParams.add(SoundParameter.treble(params.getInt(
+ SoundQuality.PARAMETER_TREBLE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SURROUND_SOUND)) {
+ soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_SURROUND_SOUND)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS)) {
+ soundParams.add(SoundParameter.speakersEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_SPEAKERS)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
+ soundParams.add(SoundParameter.speakersDelayMs(params.getInt(
+ SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
+ soundParams.add(SoundParameter.autoVolumeControl(params.getBoolean(
+ SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DTS_DRC)) {
+ soundParams.add(SoundParameter.dtsDrc(params.getBoolean(
+ SoundQuality.PARAMETER_DTS_DRC)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
+ soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)));
+ }
+ //TODO: equalizerDetail
+ //TODO: downmixMode
+ //TODO: enhancedAudioReturnChannelEnabled
+ //TODO: dolbyAudioProcessing
+ //TODO: dolbyDialogueEnhancer
+ //TODO: dtsVirtualX
+ //TODO: digitalOutput
+ //TODO: activeProfile
+ //TODO: soundStyle
+ return (SoundParameter[]) soundParams.toArray();
+ }
+
@Override
public List<String> getSoundProfilePackageNames(UserHandle user) {
if (!hasGlobalSoundQualityServicePermission()) {
@@ -905,24 +1197,86 @@ public class MediaQualityService extends SystemService {
@Override
public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+ if (DEBUG) {
+ Slogf.d(TAG, "registerAmbientBacklightCallback");
+ }
+
if (!hasReadColorZonesPermission()) {
//TODO: error handling
}
+
+ String callingPackageName = getPackageOfCallingUid();
+
+ synchronized (mCallbackRecords) {
+ AmbientBacklightCallbackRecord record = mCallbackRecords.get(callingPackageName);
+ if (record != null) {
+ if (record.mCallback.asBinder().equals(callback.asBinder())) {
+ Slog.w(TAG, "AmbientBacklight Callback already registered");
+ return;
+ }
+ record.release();
+ mCallbackRecords.remove(callingPackageName);
+ }
+ mCallbackRecords.put(callingPackageName,
+ new AmbientBacklightCallbackRecord(callingPackageName, callback));
+ }
}
@Override
public void setAmbientBacklightSettings(
AmbientBacklightSettings settings, UserHandle user) {
+ if (DEBUG) {
+ Slogf.d(TAG, "setAmbientBacklightSettings " + settings);
+ }
+
if (!hasReadColorZonesPermission()) {
//TODO: error handling
}
+
+ try {
+ if (mMediaQuality != null) {
+ android.hardware.tv.mediaquality.AmbientBacklightSettings halSettings =
+ new android.hardware.tv.mediaquality.AmbientBacklightSettings();
+ halSettings.uid = Binder.getCallingUid();
+ halSettings.source = (byte) settings.getSource();
+ halSettings.maxFramerate = settings.getMaxFps();
+ halSettings.colorFormat = (byte) settings.getColorFormat();
+ halSettings.hZonesNumber = settings.getHorizontalZonesCount();
+ halSettings.vZonesNumber = settings.getVerticalZonesCount();
+ halSettings.hasLetterbox = settings.isLetterboxOmitted();
+ halSettings.colorThreshold = settings.getThreshold();
+
+ mMediaQuality.setAmbientBacklightDetector(halSettings);
+
+ mHalAmbientBacklightCallback.setAmbientBacklightClientPackageName(
+ getPackageOfCallingUid());
+
+ if (DEBUG) {
+ Slogf.d(TAG, "set ambient settings package: " + halSettings.uid);
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set ambient backlight settings", e);
+ }
}
@Override
public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) {
+ if (DEBUG) {
+ Slogf.d(TAG, "setAmbientBacklightEnabled " + enabled);
+ }
if (!hasReadColorZonesPermission()) {
//TODO: error handling
}
+ try {
+ if (mMediaQuality != null) {
+ mMediaQuality.setAmbientBacklightDetectionEnabled(enabled);
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set ambient backlight enabled", e);
+ }
}
@Override
@@ -979,10 +1333,10 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
- mMediaQuality.setAutoPqEnabled(enabled);
+ if (mMediaQuality.isAutoPqSupported()) {
+ mMediaQuality.setAutoPqEnabled(enabled);
+ }
}
- } catch (UnsupportedOperationException e) {
- Slog.e(TAG, "The current device is not supported");
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set auto picture quality", e);
}
@@ -992,10 +1346,10 @@ public class MediaQualityService extends SystemService {
public boolean isAutoPictureQualityEnabled(UserHandle user) {
try {
if (mMediaQuality != null) {
- return mMediaQuality.getAutoPqEnabled();
+ if (mMediaQuality.isAutoPqSupported()) {
+ mMediaQuality.getAutoPqEnabled();
+ }
}
- } catch (UnsupportedOperationException e) {
- Slog.e(TAG, "The current device is not supported");
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get auto picture quality", e);
}
@@ -1011,12 +1365,12 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
- mMediaQuality.setAutoSrEnabled(enabled);
+ if (mMediaQuality.isAutoSrSupported()) {
+ mMediaQuality.setAutoSrEnabled(enabled);
+ }
}
- } catch (UnsupportedOperationException e) {
- Slog.e(TAG, "The current device is not supported");
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to set auto super resolution", e);
+ Slog.e(TAG, "Failed to set super resolution", e);
}
}
@@ -1024,12 +1378,12 @@ public class MediaQualityService extends SystemService {
public boolean isSuperResolutionEnabled(UserHandle user) {
try {
if (mMediaQuality != null) {
- return mMediaQuality.getAutoSrEnabled();
+ if (mMediaQuality.isAutoSrSupported()) {
+ mMediaQuality.getAutoSrEnabled();
+ }
}
- } catch (UnsupportedOperationException e) {
- Slog.e(TAG, "The current device is not supported");
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to get auto super resolution", e);
+ Slog.e(TAG, "Failed to get super resolution", e);
}
return false;
}
@@ -1043,12 +1397,12 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
- mMediaQuality.setAutoAqEnabled(enabled);
+ if (mMediaQuality.isAutoAqSupported()) {
+ mMediaQuality.setAutoAqEnabled(enabled);
+ }
}
- } catch (UnsupportedOperationException e) {
- Slog.e(TAG, "The current device is not supported");
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to set auto audio quality", e);
+ Slog.e(TAG, "Failed to set auto sound quality", e);
}
}
@@ -1056,12 +1410,12 @@ public class MediaQualityService extends SystemService {
public boolean isAutoSoundQualityEnabled(UserHandle user) {
try {
if (mMediaQuality != null) {
- return mMediaQuality.getAutoAqEnabled();
+ if (mMediaQuality.isAutoAqSupported()) {
+ mMediaQuality.getAutoAqEnabled();
+ }
}
- } catch (UnsupportedOperationException e) {
- Slog.e(TAG, "The current device is not supported");
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to get auto audio quality", e);
+ Slog.e(TAG, "Failed to get auto sound quality", e);
}
return false;
}
@@ -1119,4 +1473,167 @@ public class MediaQualityService extends SystemService {
private UserState getUserStateLocked(int userId) {
return mUserStates.get(userId);
}
+
+ private final class AmbientBacklightCallbackRecord implements IBinder.DeathRecipient {
+ final String mPackageName;
+ final IAmbientBacklightCallback mCallback;
+
+ AmbientBacklightCallbackRecord(@NonNull String pkgName,
+ @NonNull IAmbientBacklightCallback cb) {
+ mPackageName = pkgName;
+ mCallback = cb;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death", e);
+ }
+ }
+
+ void release() {
+ try {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.e(TAG, "Failed to unlink to death", e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mCallbackRecords) {
+ mCallbackRecords.remove(mPackageName);
+ }
+ }
+ }
+
+ private final class HalAmbientBacklightCallback
+ extends android.hardware.tv.mediaquality.IMediaQualityCallback.Stub {
+ private final Object mLock = new Object();
+ private String mAmbientBacklightClientPackageName;
+
+ void setAmbientBacklightClientPackageName(@NonNull String packageName) {
+ synchronized (mLock) {
+ if (TextUtils.equals(mAmbientBacklightClientPackageName, packageName)) {
+ return;
+ }
+ handleAmbientBacklightInterrupted();
+ mAmbientBacklightClientPackageName = packageName;
+ }
+ }
+
+ void handleAmbientBacklightInterrupted() {
+ synchronized (mCallbackRecords) {
+ if (mAmbientBacklightClientPackageName == null) {
+ Slog.e(TAG, "Invalid package name in interrupted event");
+ return;
+ }
+ AmbientBacklightCallbackRecord record = mCallbackRecords.get(
+ mAmbientBacklightClientPackageName);
+ if (record == null) {
+ Slog.e(TAG, "Callback record not found for ambient backlight");
+ return;
+ }
+ AmbientBacklightEvent event =
+ new AmbientBacklightEvent(
+ AMBIENT_BACKLIGHT_EVENT_INTERRUPTED, null);
+ try {
+ record.mCallback.onAmbientBacklightEvent(event);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Deliver ambient backlight interrupted event failed", e);
+ }
+ }
+ }
+
+ void handleAmbientBacklightEnabled(boolean enabled) {
+ AmbientBacklightEvent event =
+ new AmbientBacklightEvent(
+ enabled ? AMBIENT_BACKLIGHT_EVENT_ENABLED :
+ AMBIENT_BACKLIGHT_EVENT_DISABLED, null);
+ synchronized (mCallbackRecords) {
+ for (AmbientBacklightCallbackRecord record : mCallbackRecords.values()) {
+ try {
+ record.mCallback.onAmbientBacklightEvent(event);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Deliver ambient backlight enabled event failed", e);
+ }
+ }
+ }
+ }
+
+ void handleAmbientBacklightMetadataEvent(
+ @NonNull android.hardware.tv.mediaquality.AmbientBacklightMetadata
+ halMetadata) {
+ String halPackageName = mContext.getPackageManager()
+ .getNameForUid(halMetadata.settings.uid);
+ if (!TextUtils.equals(mAmbientBacklightClientPackageName, halPackageName)) {
+ Slog.e(TAG, "Invalid package name in metadata event");
+ return;
+ }
+
+ AmbientBacklightColorFormat[] zonesColorsUnion = halMetadata.zonesColors;
+ int[] zonesColorsInt = new int[zonesColorsUnion.length];
+
+ for (int i = 0; i < zonesColorsUnion.length; i++) {
+ zonesColorsInt[i] = zonesColorsUnion[i].RGB888;
+ }
+
+ AmbientBacklightMetadata metadata =
+ new AmbientBacklightMetadata(
+ halPackageName,
+ halMetadata.compressAlgorithm,
+ halMetadata.settings.source,
+ halMetadata.settings.colorFormat,
+ halMetadata.settings.hZonesNumber,
+ halMetadata.settings.vZonesNumber,
+ zonesColorsInt);
+ AmbientBacklightEvent event =
+ new AmbientBacklightEvent(
+ AMBIENT_BACKLIGHT_EVENT_METADATA_AVAILABLE, metadata);
+
+ synchronized (mCallbackRecords) {
+ AmbientBacklightCallbackRecord record = mCallbackRecords
+ .get(halPackageName);
+ if (record == null) {
+ Slog.e(TAG, "Callback record not found for ambient backlight metadata");
+ return;
+ }
+
+ try {
+ record.mCallback.onAmbientBacklightEvent(event);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Deliver ambient backlight metadata event failed", e);
+ }
+ }
+ }
+
+ @Override
+ public void notifyAmbientBacklightEvent(
+ android.hardware.tv.mediaquality.AmbientBacklightEvent halEvent) {
+ synchronized (mLock) {
+ if (halEvent.getTag() == android.hardware.tv.mediaquality
+ .AmbientBacklightEvent.Tag.enabled) {
+ boolean enabled = halEvent.getEnabled();
+ if (enabled) {
+ handleAmbientBacklightEnabled(true);
+ } else {
+ handleAmbientBacklightEnabled(false);
+ }
+ } else if (halEvent.getTag() == android.hardware.tv.mediaquality
+ .AmbientBacklightEvent.Tag.metadata) {
+ handleAmbientBacklightMetadataEvent(halEvent.getMetadata());
+ } else {
+ Slog.e(TAG, "Invalid event type in ambient backlight event");
+ }
+ }
+ }
+
+ @Override
+ public synchronized String getInterfaceHash() throws android.os.RemoteException {
+ return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.HASH;
+ }
+
+ @Override
+ public int getInterfaceVersion() throws android.os.RemoteException {
+ return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.VERSION;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index deaa8d8feae1..44d787f790cf 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -356,7 +356,7 @@ public final class PermissionPolicyService extends SystemService {
try {
manager = new PermissionControllerManager(
getUserContext(getContext(), user), PermissionThread.getHandler());
- } catch (IllegalArgumentException exception) {
+ } catch (IllegalStateException exception) {
// There's a possible race condition when a user is being removed
Log.e(LOG_TAG, "Could not create PermissionControllerManager for user"
+ user, exception);
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
index 6798a6146ae0..2452dc59bea5 100644
--- a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
+++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
@@ -17,6 +17,7 @@
package com.android.server.security.authenticationpolicy;
import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE;
+import static android.security.Flags.disableAdaptiveAuthCounterLock;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
@@ -39,6 +40,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.provider.Settings;
import android.security.authenticationpolicy.AuthenticationPolicyManager;
import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
@@ -251,6 +253,17 @@ public class AuthenticationPolicyService extends SystemService {
return;
}
+ if (disableAdaptiveAuthCounterLock() && Build.IS_DEBUGGABLE) {
+ final boolean disabled = Settings.Secure.getIntForUser(
+ getContext().getContentResolver(),
+ Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK,
+ 0 /* default */, userId) != 0;
+ if (disabled) {
+ Slog.d(TAG, "not locking (disabled by user)");
+ return;
+ }
+ }
+
//TODO: additionally consider the trust signal before locking device
lockDevice(userId);
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 40ea9319c6be..36c337c29def 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -118,6 +118,7 @@ import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.health.connect.HealthConnectManager;
import android.media.AudioManager;
import android.media.MediaDrm;
import android.media.UnsupportedSchemeException;
@@ -4115,7 +4116,7 @@ public class StatsPullAtomService extends SystemService {
int nOps = opsList.size();
for (int i = 0; i < nOps; i++) {
AppOpEntry entry = opsList.get(i);
- if (entry.mHash >= samplingRate) {
+ if (entry.mHash >= samplingRate || isHealthAppOp(entry.mOp.getOpCode())) {
continue;
}
StatsEvent e;
@@ -4301,6 +4302,11 @@ public class StatsPullAtomService extends SystemService {
return StatsManager.PULL_SUCCESS;
}
+ if (isHealthAppOp(AppOpsManager.strOpToOp(message.getOp()))) {
+ // Not log sensitive health app ops.
+ return StatsManager.PULL_SKIP;
+ }
+
pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, message.getUid(),
message.getPackageName(), "",
message.getAttributionTag() == null ? "" : message.getAttributionTag(),
@@ -5370,6 +5376,11 @@ public class StatsPullAtomService extends SystemService {
}
}
+ private boolean isHealthAppOp(int opCode) {
+ String permission = AppOpsManager.opToPermission(opCode);
+ return permission != null && HealthConnectManager.isHealthPermission(mContext, permission);
+ }
+
// Thermal event received from vendor thermal management subsystem
private static final class ThermalEventListener extends IThermalEventListener.Stub {
@Override
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 02a7db19f405..0fbf56d120a8 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -77,8 +77,9 @@ class EmulatorDisplayOverlay {
mOverlay = context.getDrawable(
com.android.internal.R.drawable.emulator_circular_window_overlay);
- mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, mScreenSize.x,
- mScreenSize.y, PixelFormat.RGBA_8888);
+ mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true);
+ mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y,
+ PixelFormat.RGBA_8888);
mSurface = mBlastBufferQueue.createSurface();
}
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index cdf6b08b1c57..b6365ad47535 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -63,8 +63,9 @@ class StrictModeFlash {
mSurfaceControl = ctrl;
mDrawNeeded = true;
- mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, 1 /* width */,
- 1 /* height */, PixelFormat.RGBA_8888);
+ mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true);
+ mBlastBufferQueue.update(mSurfaceControl, 1 /* width */, 1 /* height */,
+ PixelFormat.RGBA_8888);
mSurface = mBlastBufferQueue.createSurface();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 295759c2fc7e..7a88338d8ac5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5297,40 +5297,29 @@ class Task extends TaskFragment {
return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea());
}
- void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask,
- boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) {
- Task rTask = r.getTask();
+ void startActivityLocked(@NonNull ActivityRecord r, @Nullable Task topTask, boolean newTask,
+ boolean isTaskSwitch, @Nullable ActivityOptions options,
+ @Nullable ActivityRecord sourceRecord) {
final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
- final boolean isOrhasTask = rTask == this || hasChild(rTask);
+ final Task activityTask = r.getTask();
+ final boolean isThisOrHasChildTask = activityTask == this || hasChild(activityTask);
+
// mLaunchTaskBehind tasks get placed at the back of the task stack.
- if (!r.mLaunchTaskBehind && allowMoveToFront && (!isOrhasTask || newTask)) {
+ if (!r.mLaunchTaskBehind && allowMoveToFront && (!isThisOrHasChildTask || newTask)) {
// Last activity in task had been removed or ActivityManagerService is reusing task.
// Insert or replace.
// Might not even be in.
- positionChildAtTop(rTask);
+ positionChildAtTop(activityTask);
}
- Task task = null;
- if (!newTask && isOrhasTask && !r.shouldBeVisible()) {
+
+ if (!newTask && isThisOrHasChildTask && !r.shouldBeVisible()) {
ActivityOptions.abort(options);
return;
}
- // Place a new activity at top of root task, so it is next to interact with the user.
-
- // If we are not placing the new activity frontmost, we do not want to deliver the
- // onUserLeaving callback to the actual frontmost activity
- final Task activityTask = r.getTask();
- if (task == activityTask && mChildren.indexOf(task) != (getChildCount() - 1)) {
- mTaskSupervisor.mUserLeaving = false;
- if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
- "startActivity() behind front, mUserLeaving=false");
- }
-
- task = activityTask;
-
// Slot the activity into the history root task and proceed
- ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
- + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());
+ ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s callers: %s", r,
+ activityTask, new RuntimeException("here").fillInStackTrace());
if (isActivityTypeHomeOrRecents() && getActivityBelow(r) == null) {
// If this is the first activity, don't do any fancy animations,
@@ -5346,15 +5335,15 @@ class Task extends TaskFragment {
return;
}
- final DisplayContent dc = mDisplayContent;
- if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
- "Prepare open transition: starting " + r);
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: starting " + r);
+
+ // Place a new activity at top of root task, so it is next to interact with the user.
if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
- dc.prepareAppTransition(TRANSIT_NONE);
+ mDisplayContent.prepareAppTransition(TRANSIT_NONE);
mTaskSupervisor.mNoAnimActivities.add(r);
mTransitionController.setNoAnimation(r);
} else {
- dc.prepareAppTransition(TRANSIT_OPEN);
+ mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
mTaskSupervisor.mNoAnimActivities.remove(r);
}
if (newTask && !r.mLaunchTaskBehind) {
@@ -5405,8 +5394,7 @@ class Task extends TaskFragment {
// "has the same starting icon" as the next one. This allows the
// window manager to keep the previous window it had previously
// created, if it still had one.
- Task baseTask = r.getTask();
- final ActivityRecord prev = baseTask.getActivity(
+ final ActivityRecord prev = activityTask.getActivity(
a -> a.mStartingData != null && a.showToCurrentUser());
mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,
isTaskSwitch, sourceRecord);
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index 9780d3317e11..eb6eeb31e8fb 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -126,8 +126,9 @@ class Watermark {
} catch (OutOfResourcesException e) {
}
mSurfaceControl = ctrl;
- mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, 1 /* width */,
- 1 /* height */, PixelFormat.RGBA_8888);
+ mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true);
+ mBlastBufferQueue.update(mSurfaceControl, 1 /* width */, 1 /* height */,
+ PixelFormat.RGBA_8888);
mSurface = mBlastBufferQueue.createSurface();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 36d52ddb40e6..1754d7346220 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -753,8 +753,6 @@ public class WindowManagerService extends IWindowManager.Stub
final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
- /** Indicates that the system server is actively demanding the screen be frozen. */
- boolean mClientFreezingScreen = false;
int mAppsFreezingScreen = 0;
@VisibleForTesting
@@ -3354,60 +3352,6 @@ public class WindowManagerService extends IWindowManager.Stub
return getDefaultDisplayContentLocked().mAppTransition.isIdle();
}
-
- // -------------------------------------------------------------
- // Misc IWindowSession methods
- // -------------------------------------------------------------
-
- /** Freeze the screen during a user-switch event. Called by UserController. */
- @Override
- public void startFreezingScreen(int exitAnim, int enterAnim) {
- if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN,
- "startFreezingScreen()")) {
- throw new SecurityException("Requires FREEZE_SCREEN permission");
- }
-
- synchronized (mGlobalLock) {
- if (!mClientFreezingScreen) {
- mClientFreezingScreen = true;
- final long origId = Binder.clearCallingIdentity();
- try {
- startFreezingDisplay(exitAnim, enterAnim);
- mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.CLIENT_FREEZE_TIMEOUT, 5000);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
- }
-
- /**
- * No longer actively demand that the screen remain frozen.
- * Called by UserController after a user-switch.
- * This doesn't necessarily immediately unlock the screen; it just allows it if we're ready.
- */
- @Override
- public void stopFreezingScreen() {
- if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN,
- "stopFreezingScreen()")) {
- throw new SecurityException("Requires FREEZE_SCREEN permission");
- }
-
- synchronized (mGlobalLock) {
- if (mClientFreezingScreen) {
- mClientFreezingScreen = false;
- mLastFinishedFreezeSource = "client";
- final long origId = Binder.clearCallingIdentity();
- try {
- stopFreezingDisplayLocked();
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
- }
-
@Override
public void disableKeyguard(IBinder token, String tag, int userId) {
userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
@@ -5669,7 +5613,6 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
public static final int SHOW_STRICT_MODE_VIOLATION = 25;
- public static final int CLIENT_FREEZE_TIMEOUT = 30;
public static final int NOTIFY_ACTIVITY_DRAWN = 32;
public static final int NEW_ANIMATOR_SCALE = 34;
@@ -5759,17 +5702,6 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
- case CLIENT_FREEZE_TIMEOUT: {
- synchronized (mGlobalLock) {
- if (mClientFreezingScreen) {
- mClientFreezingScreen = false;
- mLastFinishedFreezeSource = "client-timeout";
- stopFreezingDisplayLocked();
- }
- }
- break;
- }
-
case REPORT_WINDOWS_CHANGE: {
if (mWindowsChanged) {
synchronized (mGlobalLock) {
@@ -6552,14 +6484,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
|| mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
- || mClientFreezingScreen || numOpeningApps > 0) {
+ || numOpeningApps > 0) {
ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
+ "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
+ "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
- + "mClientFreezingScreen=%b, mOpeningApps.size()=%d",
+ + "mOpeningApps.size()=%d",
waitingForConfig, waitingForRemoteDisplayChange,
mAppsFreezingScreen, mWindowsFreezingScreen,
- mClientFreezingScreen, numOpeningApps);
+ numOpeningApps);
return;
}
@@ -6589,7 +6521,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
ProtoLog.i(WM_ERROR, "%s", sb.toString());
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
if (PROFILE_ORIENTATION) {
Debug.stopMethodTracing();
}
@@ -7096,7 +7027,6 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mTransactionSequence="); pw.println(mTransactionSequence);
pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen);
pw.print(" windows="); pw.print(mWindowsFreezingScreen);
- pw.print(" client="); pw.print(mClientFreezingScreen);
pw.print(" apps="); pw.println(mAppsFreezingScreen);
final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
pw.print(" mRotation="); pw.println(defaultDisplayContent.getRotation());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index 464fee2bfc11..fb31cfe762f2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
+import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES;
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
@@ -39,6 +40,10 @@ import android.content.Context;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Looper;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.util.SparseArray;
import android.view.Display;
@@ -55,12 +60,14 @@ import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
+import com.android.server.accessibility.magnification.MagnificationKeyHandler;
import com.android.server.accessibility.magnification.MagnificationProcessor;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -88,8 +95,16 @@ public class AccessibilityInputFilterTest {
| FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_FILTER_KEY_EVENTS;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
// The expected order of EventStreamTransformations.
private final Class[] mExpectedEventHandlerTypes =
+ {MagnificationKeyHandler.class, KeyboardInterceptor.class, MotionEventInjector.class,
+ FullScreenMagnificationGestureHandler.class, TouchExplorer.class,
+ AutoclickController.class, AccessibilityInputFilter.class};
+
+ private final Class[] mExpectedEventHandlerTypesWithoutMagKeyboard =
{KeyboardInterceptor.class, MotionEventInjector.class,
FullScreenMagnificationGestureHandler.class, TouchExplorer.class,
AutoclickController.class, AccessibilityInputFilter.class};
@@ -176,6 +191,7 @@ public class AccessibilityInputFilterTest {
}
@Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() {
prepareLooper();
@@ -191,9 +207,9 @@ public class AccessibilityInputFilterTest {
EventStreamTransformation next = mEventHandler.get(SECOND_DISPLAY);
assertNotNull(next);
- // Start from index 1 because KeyboardInterceptor only exists in EventHandler for
- // DEFAULT_DISPLAY.
- for (int i = 1; next != null; i++) {
+ // Start from index 2 because KeyboardInterceptor and MagnificationKeyHandler only exist in
+ // EventHandler for DEFAULT_DISPLAY.
+ for (int i = 2; next != null; i++) {
assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
next = next.getNext();
}
@@ -232,6 +248,7 @@ public class AccessibilityInputFilterTest {
}
@Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() {
prepareLooper();
@@ -248,10 +265,36 @@ public class AccessibilityInputFilterTest {
}
next = mEventHandler.get(SECOND_DISPLAY);
+ // Start from index 2 because KeyboardInterceptor and MagnificationKeyHandler only exist
+ // in EventHandler for DEFAULT_DISPLAY.
+ for (int i = 2; next != null; i++) {
+ assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+ next = next.getNext();
+ }
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation_noMagKeys() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(2, mEventHandler.size());
+
+ // Check if mEventHandler for each display has correct order of the
+ // EventStreamTransformations.
+ EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY);
+ for (int i = 0; next != null; i++) {
+ assertEquals(next.getClass(), mExpectedEventHandlerTypesWithoutMagKeyboard[i]);
+ next = next.getNext();
+ }
+
+ next = mEventHandler.get(SECOND_DISPLAY);
// Start from index 1 because KeyboardInterceptor only exists in EventHandler for
// DEFAULT_DISPLAY.
for (int i = 1; next != null; i++) {
- assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+ assertEquals(next.getClass(), mExpectedEventHandlerTypesWithoutMagKeyboard[i]);
next = next.getNext();
}
}
@@ -387,7 +430,6 @@ public class AccessibilityInputFilterTest {
assertNotNull(handler);
assertEquals(WindowMagnificationGestureHandler.class, handler.getClass());
assertEquals(nextEventStream.getClass(), handler.getNext().getClass());
-
}
@Test public void
@@ -412,6 +454,32 @@ public class AccessibilityInputFilterTest {
assertEquals(nextEventStream.getClass(), handler.getNext().getClass());
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationKeyHandler() {
+ prepareLooper();
+ doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW).when(
+ mAms).getMagnificationMode(DEFAULT_DISPLAY);
+
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+
+ MagnificationKeyHandler handler = getMagnificationKeyHandlerFromEventHandler();
+ assertNotNull(handler);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void testEnabledFeatures_fullscreenMagnificationMode_expectedMagnificationKeyHandler() {
+ prepareLooper();
+ doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN).when(
+ mAms).getMagnificationMode(DEFAULT_DISPLAY);
+
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+
+ MagnificationKeyHandler handler = getMagnificationKeyHandlerFromEventHandler();
+ assertNotNull(handler);
+ }
+
private static void prepareLooper() {
if (Looper.myLooper() == null) {
Looper.prepare();
@@ -458,4 +526,16 @@ public class AccessibilityInputFilterTest {
}
return null;
}
+
+ @Nullable
+ private MagnificationKeyHandler getMagnificationKeyHandlerFromEventHandler() {
+ EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY);
+ while (next != null) {
+ if (next instanceof MagnificationKeyHandler) {
+ return (MagnificationKeyHandler) next;
+ }
+ next = next.getNext();
+ }
+ return null;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java
new file mode 100644
index 000000000000..d1ef33d8fb70
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.server.accessibility.magnification;
+
+import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES;
+import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_DOWN;
+import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_LEFT;
+import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_RIGHT;
+import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_UP;
+import static com.android.server.accessibility.magnification.MagnificationController.ZOOM_DIRECTION_IN;
+import static com.android.server.accessibility.magnification.MagnificationController.ZOOM_DIRECTION_OUT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Display;
+import android.view.KeyEvent;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.accessibility.EventStreamTransformation;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link MagnificationKeyHandler}.
+ */
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+public class MagnificationKeyHandlerTest {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private MagnificationKeyHandler mMkh;
+
+ @Mock
+ MagnificationKeyHandler.Callback mCallback;
+
+ @Mock
+ EventStreamTransformation mNextHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mMkh = new MagnificationKeyHandler(mCallback);
+ mMkh.setNext(mNextHandler);
+ }
+
+ @Test
+ public void onKeyEvent_unusedKeyPress_sendToNext() {
+ final KeyEvent event = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_L, 0, 0);
+ mMkh.onKeyEvent(event, 0);
+
+ // No callbacks were called.
+ verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt());
+ verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt());
+
+ // The event was passed on.
+ verify(mNextHandler, times(1)).onKeyEvent(event, 0);
+ }
+
+ @Test
+ public void onKeyEvent_arrowKeyPressWithIncorrectModifiers_sendToNext() {
+ final KeyEvent event =
+ new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT,
+ 0, KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(event, 0);
+
+ // No callbacks were called.
+ verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt());
+ verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt());
+
+ // The event was passed on.
+ verify(mNextHandler, times(1)).onKeyEvent(event, 0);
+ }
+
+ @Test
+ public void onKeyEvent_unusedKeyPressWithCorrectModifiers_sendToNext() {
+ final KeyEvent event =
+ new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_J, 0,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(event, 0);
+
+ // No callbacks were called.
+ verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt());
+ verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt());
+
+ // The event was passed on.
+ verify(mNextHandler, times(1)).onKeyEvent(event, 0);
+ }
+
+ @Test
+ public void onKeyEvent_panStartAndEnd_left() {
+ testPanMagnification(KeyEvent.KEYCODE_DPAD_LEFT, PAN_DIRECTION_LEFT);
+ }
+
+ @Test
+ public void onKeyEvent_panStartAndEnd_right() {
+ testPanMagnification(KeyEvent.KEYCODE_DPAD_RIGHT, PAN_DIRECTION_RIGHT);
+ }
+
+ @Test
+ public void onKeyEvent_panStartAndEnd_up() {
+ testPanMagnification(KeyEvent.KEYCODE_DPAD_UP, PAN_DIRECTION_UP);
+ }
+
+ @Test
+ public void onKeyEvent_panStartAndEnd_down() {
+ testPanMagnification(KeyEvent.KEYCODE_DPAD_DOWN, PAN_DIRECTION_DOWN);
+ }
+
+ @Test
+ public void onKeyEvent_scaleStartAndEnd_zoomIn() {
+ testScaleMagnification(KeyEvent.KEYCODE_EQUALS, ZOOM_DIRECTION_IN);
+ }
+
+ @Test
+ public void onKeyEvent_scaleStartAndEnd_zoomOut() {
+ testScaleMagnification(KeyEvent.KEYCODE_MINUS, ZOOM_DIRECTION_OUT);
+ }
+
+ @Test
+ public void onKeyEvent_panStartAndStop_diagonal() {
+ final KeyEvent downLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(downLeftEvent, 0);
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_LEFT);
+ verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt());
+
+ // Also press the down arrow key.
+ final KeyEvent downDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(downDownEvent, 0);
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_LEFT);
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_DOWN);
+ verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt());
+
+ // Lift the left arrow key.
+ final KeyEvent upLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(upLeftEvent, 0);
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_LEFT);
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_DOWN);
+ verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_LEFT);
+ verify(mCallback, times(0)).onPanMagnificationStop(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_DOWN);
+
+ // Lift the down arrow key.
+ final KeyEvent upDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(upDownEvent, 0);
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_LEFT);
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_DOWN);
+ verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_LEFT);
+ verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY,
+ PAN_DIRECTION_DOWN);
+
+ // The event was not passed on.
+ verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt());
+ }
+
+ private void testPanMagnification(int keyCode, int panDirection) {
+ final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(downEvent, 0);
+
+ // Pan started.
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection);
+ verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt());
+
+ final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(upEvent, 0);
+
+ // Pan ended.
+ verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection);
+ verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, panDirection);
+
+ // Scale callbacks were not called.
+ verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt());
+
+ // The events were not passed on.
+ verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt());
+ }
+
+ private void testScaleMagnification(int keyCode, int zoomDirection) {
+ final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(downEvent, 0);
+
+ // Scale started.
+ verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY,
+ zoomDirection);
+ verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt());
+
+ final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON);
+ mMkh.onKeyEvent(upEvent, 0);
+
+ // Scale ended.
+ verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY,
+ zoomDirection);
+ verify(mCallback, times(1)).onScaleMagnificationStop(Display.DEFAULT_DISPLAY,
+ zoomDirection);
+
+ // Pan callbacks were not called.
+ verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt());
+ verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt());
+
+ // The events were not passed on.
+ verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt());
+
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index eb4a628e14e5..792faab5b196 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -25,6 +25,7 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_DEVICE_POWER_ON;
+import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_POWER_STATE_CHANGE;
import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_REPORT_POWER_STATUS;
import static com.google.common.truth.Truth.assertThat;
@@ -230,11 +231,15 @@ public class DeviceSelectActionFromTvTest {
"testDeviceSelect");
action.start();
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
mNativeWrapper.clearResultMessages();
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.processCommand(REPORT_POWER_STATUS_ON);
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -249,10 +254,14 @@ public class DeviceSelectActionFromTvTest {
/*isCec20=*/false);
action.start();
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.processCommand(REPORT_POWER_STATUS_STANDBY);
mTestLooper.dispatchAll();
+
HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER);
assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed);
@@ -261,6 +270,7 @@ public class DeviceSelectActionFromTvTest {
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.processCommand(REPORT_POWER_STATUS_ON);
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -275,8 +285,11 @@ public class DeviceSelectActionFromTvTest {
/*isCec20=*/false);
action.start();
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
mNativeWrapper.clearResultMessages();
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.processCommand(REPORT_POWER_STATUS_STANDBY);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
@@ -288,6 +301,7 @@ public class DeviceSelectActionFromTvTest {
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.processCommand(REPORT_POWER_STATUS_ON);
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -302,8 +316,11 @@ public class DeviceSelectActionFromTvTest {
/*isCec20=*/false);
action.start();
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
mNativeWrapper.clearResultMessages();
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.processCommand(REPORT_POWER_STATUS_STANDBY);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
@@ -316,6 +333,7 @@ public class DeviceSelectActionFromTvTest {
action.handleTimerEvent(STATE_WAIT_FOR_REPORT_POWER_STATUS);
// Give up getting power status, and just send <Set Stream Path>
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -332,7 +350,10 @@ public class DeviceSelectActionFromTvTest {
"testDeviceSelect");
action.start();
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -348,11 +369,15 @@ public class DeviceSelectActionFromTvTest {
"testDeviceSelect");
action.start();
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
mNativeWrapper.clearResultMessages();
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.processCommand(REPORT_POWER_STATUS_ON);
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -369,10 +394,14 @@ public class DeviceSelectActionFromTvTest {
/*isCec20=*/true);
action.start();
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.processCommand(REPORT_POWER_STATUS_STANDBY);
mTestLooper.dispatchAll();
+
HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(userControlPressed);
@@ -381,6 +410,7 @@ public class DeviceSelectActionFromTvTest {
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.processCommand(REPORT_POWER_STATUS_ON);
mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
index ee8eb9b35088..b76e0bc8cd14 100644
--- a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
@@ -42,8 +42,10 @@ import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.events.AuthenticationFailedInfo;
import android.hardware.biometrics.events.AuthenticationSucceededInfo;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
@@ -151,6 +153,8 @@ public class AuthenticationPolicyServiceTest {
when(mSecureLockDeviceService.disableSecureLockDevice(any()))
.thenReturn(ERROR_UNSUPPORTED);
}
+
+ toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, false /* disable */);
}
@After
@@ -252,8 +256,24 @@ public class AuthenticationPolicyServiceTest {
}
@Test
- public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked()
+ @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
+ public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockEnabled()
+ throws RemoteException {
+ testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked(
+ true /* enabled */);
+ }
+
+ @Test
+ @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
+ public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockDisabled()
throws RemoteException {
+ toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */);
+ testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked(
+ false /* enabled */);
+ }
+
+ private void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked(
+ boolean enabled) throws RemoteException {
// Device is currently not locked and Keyguard is not showing
when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false);
when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
@@ -264,7 +284,11 @@ public class AuthenticationPolicyServiceTest {
}
waitForAuthCompletion();
- verifyLockDevice(PRIMARY_USER_ID);
+ if (enabled) {
+ verifyLockDevice(PRIMARY_USER_ID);
+ } else {
+ verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID);
+ }
}
@Test
@@ -300,8 +324,24 @@ public class AuthenticationPolicyServiceTest {
}
@Test
- public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser()
+ @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
+ public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockEnabled()
throws RemoteException {
+ testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser(
+ true /* enabled */);
+ }
+
+ @Test
+ @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
+ public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockDisabled()
+ throws RemoteException {
+ toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */);
+ testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser(
+ false /* enabled */);
+ }
+
+ private void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser(
+ boolean enabled) throws RemoteException {
// Three failed primary auth attempts
for (int i = 0; i < 3; i++) {
mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID);
@@ -313,7 +353,11 @@ public class AuthenticationPolicyServiceTest {
}
waitForAuthCompletion();
- verifyLockDevice(PRIMARY_USER_ID);
+ if (enabled) {
+ verifyLockDevice(PRIMARY_USER_ID);
+ } else {
+ verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID);
+ }
}
@Test
@@ -366,10 +410,13 @@ public class AuthenticationPolicyServiceTest {
REASON_UNKNOWN, true, userId).build();
}
-
private AuthenticationFailedInfo authFailedInfo(int userId) {
return new AuthenticationFailedInfo.Builder(BiometricSourceType.FINGERPRINT, REASON_UNKNOWN,
userId).build();
}
+ private void toggleAdaptiveAuthSettingsOverride(int userId, boolean disable) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, disable ? 1 : 0, userId);
+ }
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
index 819e73df955b..6dda7ea3eb59 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -48,6 +48,13 @@ public abstract class UsbACTerminal extends UsbACInterface {
return mAssocTerminal;
}
+ public boolean isInputTerminal() {
+ return mTerminalType == UsbTerminalTypes.TERMINAL_IN_MIC
+ || mTerminalType == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET
+ || mTerminalType == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED
+ || mTerminalType == UsbTerminalTypes.TERMINAL_EXTERN_LINE;
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
mTerminalID = stream.getByte();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index ba178845a536..bfa4ecd71f5a 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -524,27 +524,21 @@ public final class UsbDescriptorParser {
* @hide
*/
public boolean hasMic() {
- boolean hasMic = false;
-
ArrayList<UsbDescriptor> acDescriptors =
getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
UsbACInterface.AUDIO_AUDIOCONTROL);
for (UsbDescriptor descriptor : acDescriptors) {
if (descriptor instanceof UsbACTerminal) {
UsbACTerminal inDescr = (UsbACTerminal) descriptor;
- if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC
- || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET
- || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED
- || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) {
- hasMic = true;
- break;
+ if (inDescr.isInputTerminal()) {
+ return true;
}
} else {
Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
+ " t:0x" + Integer.toHexString(descriptor.getType()));
}
}
- return hasMic;
+ return false;
}
/**
@@ -913,18 +907,20 @@ public final class UsbDescriptorParser {
float probability = 0.0f;
- // Look for a microphone
- boolean hasMic = hasMic();
-
// Look for a "speaker"
boolean hasSpeaker = hasSpeaker();
- if (hasMic && hasSpeaker) {
- probability += 0.75f;
- }
-
- if (hasMic && hasHIDInterface()) {
- probability += 0.25f;
+ if (hasMic()) {
+ if (hasSpeaker) {
+ probability += 0.75f;
+ }
+ if (hasHIDInterface()) {
+ probability += 0.25f;
+ }
+ if (getMaximumInputChannelCount() > 1) {
+ // A headset is more likely to only support mono capture.
+ probability -= 0.25f;
+ }
}
return probability;
@@ -935,9 +931,11 @@ public final class UsbDescriptorParser {
* headset. The probability range is between 0.0f (definitely NOT a headset) and
* 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
* to count on the peripheral being a headset.
+ * To align with the output device type, only treat the device as input headset if it is
+ * an output headset.
*/
public boolean isInputHeadset() {
- return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
+ return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER && isOutputHeadset();
}
// TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here.
@@ -952,6 +950,32 @@ public final class UsbDescriptorParser {
return maxChannelCount;
}
+ private int getMaximumInputChannelCount() {
+ int maxChannelCount = 0;
+ ArrayList<UsbDescriptor> acDescriptors =
+ getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
+ UsbACInterface.AUDIO_AUDIOCONTROL);
+ for (UsbDescriptor descriptor : acDescriptors) {
+ if (!(descriptor instanceof UsbACTerminal)) {
+ continue;
+ }
+ UsbACTerminal inDescr = (UsbACTerminal) descriptor;
+ if (!inDescr.isInputTerminal()) {
+ continue;
+ }
+ // For an input terminal, it should at lease has 1 channel.
+ // Comparing the max channel count with 1 here in case the USB device doesn't report
+ // audio channel cluster.
+ maxChannelCount = Math.max(maxChannelCount, 1);
+ if (!(descriptor instanceof UsbAudioChannelCluster)) {
+ continue;
+ }
+ maxChannelCount = Math.max(maxChannelCount,
+ ((UsbAudioChannelCluster) descriptor).getChannelCount());
+ }
+ return maxChannelCount;
+ }
+
/**
* @hide
*/
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 8c04f647fb2f..e0532633d40b 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -736,30 +736,6 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + ALT + '-' -> Magnification Zoom Out",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_MINUS
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT,
- intArrayOf(KeyEvent.KEYCODE_MINUS),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "META + ALT + '=' -> Magnification Zoom In",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_EQUALS
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN,
- intArrayOf(KeyEvent.KEYCODE_EQUALS),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
"META + ALT + M -> Toggle Magnification",
intArrayOf(
KeyEvent.KEYCODE_META_LEFT,
@@ -784,54 +760,6 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + ALT + 'Down' -> Magnification Pan Down",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_DPAD_DOWN
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN,
- intArrayOf(KeyEvent.KEYCODE_DPAD_DOWN),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "META + ALT + 'Up' -> Magnification Pan Up",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_DPAD_UP
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP,
- intArrayOf(KeyEvent.KEYCODE_DPAD_UP),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "META + ALT + 'Left' -> Magnification Pan Left",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_DPAD_LEFT
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT,
- intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "META + ALT + 'Right' -> Magnification Pan Right",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_DPAD_RIGHT
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT,
- intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
"META + ALT + 'V' -> Toggle Voice Access",
intArrayOf(
KeyEvent.KEYCODE_META_LEFT,
diff --git a/tests/NetworkSecurityConfigTest/Android.bp b/tests/NetworkSecurityConfigTest/Android.bp
index 4c48eaa4622e..6a68f2bb7ff9 100644
--- a/tests/NetworkSecurityConfigTest/Android.bp
+++ b/tests/NetworkSecurityConfigTest/Android.bp
@@ -11,11 +11,12 @@ android_test {
name: "NetworkSecurityConfigTests",
certificate: "platform",
libs: [
- "android.test.runner.stubs.system",
"android.test.base.stubs.system",
+ "android.test.runner.stubs.system",
],
static_libs: ["junit"],
// Include all test java files.
srcs: ["src/**/*.java"],
platform_apis: true,
+ test_suites: ["general-tests"],
}
diff --git a/tests/NetworkSecurityConfigTest/TEST_MAPPING b/tests/NetworkSecurityConfigTest/TEST_MAPPING
new file mode 100644
index 000000000000..d1b9aa1e3b53
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "NetworkSecurityConfigTests"
+ }
+ ]
+}