diff options
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" + } + ] +} |