diff options
98 files changed, 1885 insertions, 2118 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 80578e4e40f6..ac679095faa0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1765,15 +1765,12 @@ package android.hardware.input { public final class InputManager { method public void addUniqueIdAssociation(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings(); - method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier); - method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice); + method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors(); method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping(); method public int getMousePointerSpeed(); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int); - method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String); method public void removeUniqueIdAssociation(@NonNull String); - method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String); field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL } diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 2816f777e8ab..1c37aa25af5f 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -94,33 +94,8 @@ interface IInputManager { // Keyboard layouts configuration. KeyboardLayout[] getKeyboardLayouts(); - KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier); - KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor); - String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier); - - @EnforcePermission("SET_KEYBOARD_LAYOUT") - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " - + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)") - void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor); - - String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier); - - @EnforcePermission("SET_KEYBOARD_LAYOUT") - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " - + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)") - void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor); - - @EnforcePermission("SET_KEYBOARD_LAYOUT") - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " - + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)") - void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor); - - // New Keyboard layout config APIs KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index a1242fb43bbd..f94915876a2f 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -473,22 +473,21 @@ public final class InputManager { } /** - * Returns the descriptors of all supported keyboard layouts appropriate for the specified - * input device. + * Returns the descriptors of all supported keyboard layouts. * <p> * The input manager consults the built-in keyboard layouts as well as all keyboard layouts * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. * </p> * - * @param device The input device to query. * @return The ids of all keyboard layouts which are supported by the specified input device. * * @hide */ @TestApi @NonNull - public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) { - KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier()); + @SuppressLint("UnflaggedApi") + public List<String> getKeyboardLayoutDescriptors() { + KeyboardLayout[] layouts = getKeyboardLayouts(); List<String> res = new ArrayList<>(); for (KeyboardLayout kl : layouts) { res.add(kl.getDescriptor()); @@ -511,33 +510,18 @@ public final class InputManager { @TestApi @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) { - KeyboardLayout[] layouts = getKeyboardLayouts(); - for (KeyboardLayout kl : layouts) { - if (layoutDescriptor.equals(kl.getDescriptor())) { - return kl.getLayoutType(); - } - } - return ""; + KeyboardLayout layout = getKeyboardLayout(layoutDescriptor); + return layout == null ? "" : layout.getLayoutType(); } /** - * Gets information about all supported keyboard layouts appropriate - * for a specific input device. - * <p> - * The input manager consults the built-in keyboard layouts as well - * as all keyboard layouts advertised by applications using a - * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. - * </p> - * - * @return A list of all supported keyboard layouts for a specific - * input device. - * + * TODO(b/330517633): Cleanup the unsupported API * @hide */ @NonNull public KeyboardLayout[] getKeyboardLayoutsForInputDevice( @NonNull InputDeviceIdentifier identifier) { - return mGlobal.getKeyboardLayoutsForInputDevice(identifier); + return new KeyboardLayout[0]; } /** @@ -549,6 +533,7 @@ public final class InputManager { * * @hide */ + @Nullable public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { if (keyboardLayoutDescriptor == null) { throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); @@ -562,121 +547,45 @@ public final class InputManager { } /** - * Gets the current keyboard layout descriptor for the specified input device. - * - * @param identifier Identifier for the input device - * @return The keyboard layout descriptor, or null if no keyboard layout has been set. - * + * TODO(b/330517633): Cleanup the unsupported API * @hide */ - @TestApi @Nullable public String getCurrentKeyboardLayoutForInputDevice( @NonNull InputDeviceIdentifier identifier) { - try { - return mIm.getCurrentKeyboardLayoutForInputDevice(identifier); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } + return null; } /** - * Sets the current keyboard layout descriptor for the specified input device. - * <p> - * This method may have the side-effect of causing the input device in question to be - * reconfigured. - * </p> - * - * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null. - * + * TODO(b/330517633): Cleanup the unsupported API * @hide */ - @TestApi - @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, - @NonNull String keyboardLayoutDescriptor) { - mGlobal.setCurrentKeyboardLayoutForInputDevice(identifier, - keyboardLayoutDescriptor); - } + @NonNull String keyboardLayoutDescriptor) {} /** - * Gets all keyboard layout descriptors that are enabled for the specified input device. - * - * @param identifier The identifier for the input device. - * @return The keyboard layout descriptors. - * + * TODO(b/330517633): Cleanup the unsupported API * @hide */ public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { - if (identifier == null) { - throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); - } - - try { - return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } + return new String[0]; } /** - * Adds the keyboard layout descriptor for the specified input device. - * <p> - * This method may have the side-effect of causing the input device in question to be - * reconfigured. - * </p> - * - * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add. - * + * TODO(b/330517633): Cleanup the unsupported API * @hide */ - @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { - if (identifier == null) { - throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); - } - if (keyboardLayoutDescriptor == null) { - throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); - } - - try { - mIm.addKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } } /** - * Removes the keyboard layout descriptor for the specified input device. - * <p> - * This method may have the side-effect of causing the input device in question to be - * reconfigured. - * </p> - * - * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove. - * + * TODO(b/330517633): Cleanup the unsupported API * @hide */ - @TestApi @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, @NonNull String keyboardLayoutDescriptor) { - if (identifier == null) { - throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); - } - if (keyboardLayoutDescriptor == null) { - throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); - } - - try { - mIm.removeKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } } /** diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index 7c104a0ca946..7b29666d9a96 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -1068,36 +1068,21 @@ public final class InputManagerGlobal { } /** - * @see InputManager#getKeyboardLayoutsForInputDevice(InputDeviceIdentifier) + * TODO(b/330517633): Cleanup the unsupported API */ @NonNull public KeyboardLayout[] getKeyboardLayoutsForInputDevice( @NonNull InputDeviceIdentifier identifier) { - try { - return mIm.getKeyboardLayoutsForInputDevice(identifier); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } + return new KeyboardLayout[0]; } /** - * @see InputManager#setCurrentKeyboardLayoutForInputDevice - * (InputDeviceIdentifier, String) + * TODO(b/330517633): Cleanup the unsupported API */ - @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice( @NonNull InputDeviceIdentifier identifier, - @NonNull String keyboardLayoutDescriptor) { - Objects.requireNonNull(identifier, "identifier must not be null"); - Objects.requireNonNull(keyboardLayoutDescriptor, - "keyboardLayoutDescriptor must not be null"); - try { - mIm.setCurrentKeyboardLayoutForInputDevice(identifier, - keyboardLayoutDescriptor); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } + @NonNull String keyboardLayoutDescriptor) {} + /** * @see InputDevice#getSensorManager() diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 0fc51e74d570..582c90feb547 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -27,12 +27,15 @@ import static android.graphics.Matrix.MSKEW_Y; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION; import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents; +import static com.android.window.flags.Flags.offloadColorExtraction; import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.MainThread; import android.annotation.NonNull; @@ -832,6 +835,15 @@ public abstract class WallpaperService extends Service { } /** + * Called when the dim amount of the wallpaper changed. This can be used to recompute the + * wallpaper colors based on the new dim, and call {@link #notifyColorsChanged()}. + * @hide + */ + @FlaggedApi(FLAG_OFFLOAD_COLOR_EXTRACTION) + public void onDimAmountChanged(float dimAmount) { + } + + /** * Called when an application has changed the desired virtual size of * the wallpaper. */ @@ -1043,6 +1055,10 @@ public abstract class WallpaperService extends Service { } mPreviousWallpaperDimAmount = mWallpaperDimAmount; + + // after the dim changes, allow colors to be immediately recomputed + mLastColorInvalidation = 0; + if (offloadColorExtraction()) onDimAmountChanged(mWallpaperDimAmount); } /** diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index b30002228d54..6caf4d6ff992 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -196,11 +196,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { if (!super.setControl(control, showTypes, hideTypes)) { return false; } - final boolean hasLeash = control != null && control.getLeash() != null; - if (!hasLeash && !mIsRequestedVisibleAwaitingLeash) { + if (control == null && !mIsRequestedVisibleAwaitingLeash) { mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType()); removeSurface(); } + final boolean hasLeash = control != null && control.getLeash() != null; if (hasLeash) { mIsRequestedVisibleAwaitingLeash = false; } diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index efe31fff411e..616004b78dba 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -36,3 +36,10 @@ flag { description: "Enables a limit on the number of Tasks shown in Desktop Mode" bug: "332502912" } + +flag { + name: "enable_windowing_edge_drag_resize" + namespace: "lse_desktop_experience" + description: "Enables edge drag resizing for all input sources" + bug: "323383067" +} diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig index aa92af228862..150b04e87d97 100644 --- a/core/java/android/window/flags/wallpaper_manager.aconfig +++ b/core/java/android/window/flags/wallpaper_manager.aconfig @@ -21,4 +21,14 @@ flag { namespace: "systemui" description: "Prevent the system from sending consecutive onVisibilityChanged(false) events." bug: "285631818" +} + +flag { + name: "offload_color_extraction" + namespace: "systemui" + description: "Let ImageWallpaper take care of its wallpaper color extraction, instead of system_server" + bug: "328791519" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index dd6b772ab871..87c47da16b9a 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -128,9 +128,28 @@ flag { } flag { + name: "fifo_priority_for_major_ui_processes" + namespace: "windowing_frontend" + description: "Use realtime priority for SystemUI and launcher" + bug: "288140556" + is_fixed_read_only: true +} + +flag { name: "insets_decoupled_configuration" namespace: "windowing_frontend" description: "Configuration decoupled from insets" bug: "151861875" is_fixed_read_only: true +} + +flag { + name: "keyguard_appear_transition" + namespace: "windowing_frontend" + description: "Add transition when keyguard appears" + bug: "327970608" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb Binary files differindex 826adc39c19a..97147a01b259 100644 --- a/data/etc/core.protolog.pb +++ b/data/etc/core.protolog.pb diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index d410d5f5400e..6cf12deea928 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -709,18 +709,6 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, - "2959074735946674755": { - "message": "Trying to update display configuration for system\/invalid process.", - "level": "WARN", - "group": "WM_DEBUG_CONFIGURATION", - "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" - }, - "5668810920995272206": { - "message": "Trying to update display configuration for invalid process, pid=%d", - "level": "WARN", - "group": "WM_DEBUG_CONFIGURATION", - "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" - }, "-1123414663662718691": { "message": "setVr2dDisplayId called for: %d", "level": "DEBUG", @@ -3469,6 +3457,12 @@ "group": "WM_DEBUG_WALLPAPER", "at": "com\/android\/server\/wm\/WallpaperController.java" }, + "257349083882992098": { + "message": "updateWallpaperTokens requestedVisibility=%b on keyguardLocked=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "7408402065665963407": { "message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b", "level": "VERBOSE", diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index f9347ee6ea09..e8b4104a33bb 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -424,12 +424,14 @@ key 580 APP_SWITCH key 582 VOICE_ASSIST # Linux KEY_ASSISTANT key 583 ASSIST +key 585 EMOJI_PICKER key 656 MACRO_1 key 657 MACRO_2 key 658 MACRO_3 key 659 MACRO_4 # Keys defined by HID usages +key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 313d0d24b459..d2958779c0d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -455,8 +455,7 @@ public class BubbleController implements ConfigurationChangeListener, ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s", task.taskId, b.getKey()); - mBubbleData.setSelectedBubble(b); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(b); return; } } @@ -593,13 +592,6 @@ public class BubbleController implements ConfigurationChangeListener, } } - private void openBubbleOverflow() { - ensureBubbleViewsAndWindowCreated(); - mBubbleData.setShowingOverflow(true); - mBubbleData.setSelectedBubble(mBubbleData.getOverflow()); - mBubbleData.setExpanded(true); - } - /** * Called when the status bar has become visible or invisible (either permanently or * temporarily). @@ -1247,8 +1239,7 @@ public class BubbleController implements ConfigurationChangeListener, } if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) { // already in the stack - mBubbleData.setSelectedBubble(b); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(b); } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) { // promote it out of the overflow promoteBubbleFromOverflow(b); @@ -1273,8 +1264,7 @@ public class BubbleController implements ConfigurationChangeListener, String key = entry.getKey(); Bubble bubble = mBubbleData.getBubbleInStackWithKey(key); if (bubble != null) { - mBubbleData.setSelectedBubble(bubble); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(bubble); } else { bubble = mBubbleData.getOverflowBubbleWithKey(key); if (bubble != null) { @@ -1367,8 +1357,7 @@ public class BubbleController implements ConfigurationChangeListener, } else { // App bubble is not selected, select it & expand Log.i(TAG, " showOrHideAppBubble, expand and select existing app bubble"); - mBubbleData.setSelectedBubble(existingAppBubble); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble); } } else { // Check if it exists in the overflow diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 61f0ed22b537..ae3d0c559014 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -365,6 +365,19 @@ public class BubbleData { mSelectedBubble = bubble; } + /** + * Sets the selected bubble and expands it. + * + * <p>This dispatches a single state update for both changes and should be used instead of + * calling {@link #setSelectedBubble(BubbleViewProvider)} followed by + * {@link #setExpanded(boolean)} immediately after, which will generate 2 separate updates. + */ + public void setSelectedBubbleAndExpandStack(BubbleViewProvider bubble) { + setSelectedBubbleInternal(bubble); + setExpandedInternal(true); + dispatchPendingChanges(); + } + public void setSelectedBubble(BubbleViewProvider bubble) { setSelectedBubbleInternal(bubble); dispatchPendingChanges(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 48e396a4817f..6be411dd81d0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -1222,6 +1222,19 @@ public class BubbleDataTest extends ShellTestCase { assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT); } + @Test + public void setSelectedBubbleAndExpandStack() { + sendUpdatedEntryAtTime(mEntryA1, 1000); + sendUpdatedEntryAtTime(mEntryA2, 2000); + mBubbleData.setListener(mListener); + + mBubbleData.setSelectedBubbleAndExpandStack(mBubbleA1); + + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleA1); + assertExpandedChangedTo(true); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 76bbca620072..5aa006bce000 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -1309,18 +1309,24 @@ public final class MediaRouter2 { return; } - RoutingController newController; - if (sessionInfo.isSystemSession()) { - newController = getSystemController(); - newController.setRoutingSessionInfo(sessionInfo); + RoutingController newController = addRoutingController(sessionInfo); + notifyTransfer(oldController, newController); + } + + @NonNull + private RoutingController addRoutingController(@NonNull RoutingSessionInfo session) { + RoutingController controller; + if (session.isSystemSession()) { + // mSystemController is never released, so we only need to update its status. + mSystemController.setRoutingSessionInfo(session); + controller = mSystemController; } else { - newController = new RoutingController(sessionInfo); + controller = new RoutingController(session); synchronized (mLock) { - mNonSystemRoutingControllers.put(newController.getId(), newController); + mNonSystemRoutingControllers.put(controller.getId(), controller); } } - - notifyTransfer(oldController, newController); + return controller; } void updateControllerOnHandler(RoutingSessionInfo sessionInfo) { @@ -1329,40 +1335,12 @@ public final class MediaRouter2 { return; } - if (sessionInfo.isSystemSession()) { - // The session info is sent from SystemMediaRoute2Provider. - RoutingController systemController = getSystemController(); - systemController.setRoutingSessionInfo(sessionInfo); - notifyControllerUpdated(systemController); - return; - } - - RoutingController matchingController; - synchronized (mLock) { - matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId()); - } - - if (matchingController == null) { - Log.w( - TAG, - "updateControllerOnHandler: Matching controller not found. uniqueSessionId=" - + sessionInfo.getId()); - return; - } - - RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); - if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { - Log.w( - TAG, - "updateControllerOnHandler: Provider IDs are not matched. old=" - + oldInfo.getProviderId() - + ", new=" - + sessionInfo.getProviderId()); - return; + RoutingController controller = + getMatchingController(sessionInfo, /* logPrefix */ "updateControllerOnHandler"); + if (controller != null) { + controller.setRoutingSessionInfo(sessionInfo); + notifyControllerUpdated(controller); } - - matchingController.setRoutingSessionInfo(sessionInfo); - notifyControllerUpdated(matchingController); } void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) { @@ -1371,34 +1349,47 @@ public final class MediaRouter2 { return; } - RoutingController matchingController; - synchronized (mLock) { - matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId()); + RoutingController matchingController = + getMatchingController(sessionInfo, /* logPrefix */ "releaseControllerOnHandler"); + + if (matchingController != null) { + matchingController.releaseInternal(/* shouldReleaseSession= */ false); } + } + + @Nullable + private RoutingController getMatchingController( + RoutingSessionInfo sessionInfo, String logPrefix) { + if (sessionInfo.isSystemSession()) { + return getSystemController(); + } else { + RoutingController controller; + synchronized (mLock) { + controller = mNonSystemRoutingControllers.get(sessionInfo.getId()); + } - if (matchingController == null) { - if (DEBUG) { - Log.d( + if (controller == null) { + Log.w( TAG, - "releaseControllerOnHandler: Matching controller not found. " - + "uniqueSessionId=" + logPrefix + + ": Matching controller not found. uniqueSessionId=" + sessionInfo.getId()); + return null; } - return; - } - RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); - if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { - Log.w( - TAG, - "releaseControllerOnHandler: Provider IDs are not matched. old=" - + oldInfo.getProviderId() - + ", new=" - + sessionInfo.getProviderId()); - return; + RoutingSessionInfo oldInfo = controller.getRoutingSessionInfo(); + if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { + Log.w( + TAG, + logPrefix + + ": Provider IDs are not matched. old=" + + oldInfo.getProviderId() + + ", new=" + + sessionInfo.getProviderId()); + return null; + } + return controller; } - - matchingController.releaseInternal(/* shouldReleaseSession= */ false); } void onRequestCreateControllerByManagerOnHandler( diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 207ccbee0b50..871e9ab87299 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -80,4 +80,7 @@ interface ISessionManager { boolean hasCustomMediaSessionPolicyProvider(String componentName); int getSessionPolicies(in MediaSession.Token token); void setSessionPolicies(in MediaSession.Token token, int policies); + + // For testing of temporarily engaged sessions. + void expireTempEngagedSessions(); } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt index 8b546b4de069..791893b3c056 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt @@ -23,7 +23,6 @@ import com.android.settingslib.spa.widget.ui.SettingsSwitch @Composable fun TwoTargetSwitchPreference( model: SwitchPreferenceModel, - icon: @Composable (() -> Unit)? = null, primaryEnabled: () -> Boolean = { true }, primaryOnClick: (() -> Unit)?, ) { @@ -33,7 +32,7 @@ fun TwoTargetSwitchPreference( summary = model.summary, primaryEnabled = primaryEnabled, primaryOnClick = primaryOnClick, - icon = icon, + icon = model.icon, ) { SettingsSwitch( checked = model.checked(), diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt index 5c2d7701fd6f..1f7122e82c30 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt @@ -33,11 +33,13 @@ fun <T : AppRecord> AppListItemModel<T>.AppListTwoTargetSwitchItem( model = object : SwitchPreferenceModel { override val title = label override val summary = this@AppListTwoTargetSwitchItem.summary + override val icon = @Composable { + AppIcon(record.app, SettingsDimension.appIconItemSize) + } override val checked = checked override val changeable = changeable override val onCheckedChange = onCheckedChange }, - icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) }, primaryOnClick = onClick, ) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt index 87cd2b844a2b..c9934adfad22 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt @@ -52,6 +52,8 @@ internal class RestrictedSwitchPreferenceModel( checked = model.checked, ) + override val icon = model.icon + override val checked = when (restrictedMode) { null -> ({ null }) is NoRestricted -> model.checked diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt index e100773b2358..1bed73365e80 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt @@ -29,14 +29,12 @@ import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitc @Composable fun RestrictedTwoTargetSwitchPreference( model: SwitchPreferenceModel, - icon: @Composable (() -> Unit)? = null, restrictions: Restrictions, primaryEnabled: () -> Boolean = { true }, primaryOnClick: (() -> Unit)?, ) { RestrictedTwoTargetSwitchPreference( model = model, - icon = icon, primaryEnabled = primaryEnabled, primaryOnClick = primaryOnClick, restrictions = restrictions, @@ -48,21 +46,19 @@ fun RestrictedTwoTargetSwitchPreference( @Composable internal fun RestrictedTwoTargetSwitchPreference( model: SwitchPreferenceModel, - icon: @Composable (() -> Unit)? = null, primaryEnabled: () -> Boolean = { true }, primaryOnClick: (() -> Unit)?, restrictions: Restrictions, restrictionsProviderFactory: RestrictionsProviderFactory, ) { if (restrictions.isEmpty()) { - TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick) + TwoTargetSwitchPreference(model, primaryEnabled, primaryOnClick) return } val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel -> TwoTargetSwitchPreference( model = restrictedModel, - icon = icon, primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled), primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick), ) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 57fcc7462a65..a906875e11ef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -3,10 +3,13 @@ package com.android.settingslib.bluetooth; import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED; import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -30,6 +33,7 @@ import androidx.annotation.WorkerThread; import androidx.core.graphics.drawable.IconCompat; import com.android.settingslib.R; +import com.android.settingslib.flags.Flags; import com.android.settingslib.widget.AdaptiveIcon; import com.android.settingslib.widget.AdaptiveOutlineDrawable; @@ -46,14 +50,14 @@ public class BluetoothUtils { private static final String TAG = "BluetoothUtils"; public static final boolean V = false; // verbose logging - public static final boolean D = true; // regular logging + public static final boolean D = true; // regular logging public static final int META_INT_ERROR = -1; public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; - private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of( - "com.google.android.gms.dck"); + private static final Set<String> EXCLUSIVE_MANAGERS = + ImmutableSet.of("com.google.android.gms.dck"); private static ErrorListener sErrorListener; @@ -89,23 +93,23 @@ public class BluetoothUtils { /** * @param context to access resources from * @param cachedDevice to get class from - * @return pair containing the drawable and the description of the Bluetooth class - * of the device. + * @return pair containing the drawable and the description of the Bluetooth class of the + * device. */ - public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context, - CachedBluetoothDevice cachedDevice) { + public static Pair<Drawable, String> getBtClassDrawableWithDescription( + Context context, CachedBluetoothDevice cachedDevice) { BluetoothClass btClass = cachedDevice.getBtClass(); if (btClass != null) { switch (btClass.getMajorDeviceClass()) { case BluetoothClass.Device.Major.COMPUTER: - return new Pair<>(getBluetoothDrawable(context, - com.android.internal.R.drawable.ic_bt_laptop), + return new Pair<>( + getBluetoothDrawable( + context, com.android.internal.R.drawable.ic_bt_laptop), context.getString(R.string.bluetooth_talkback_computer)); case BluetoothClass.Device.Major.PHONE: return new Pair<>( - getBluetoothDrawable(context, - com.android.internal.R.drawable.ic_phone), + getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone), context.getString(R.string.bluetooth_talkback_phone)); case BluetoothClass.Device.Major.PERIPHERAL: @@ -115,8 +119,8 @@ public class BluetoothUtils { case BluetoothClass.Device.Major.IMAGING: return new Pair<>( - getBluetoothDrawable(context, - com.android.internal.R.drawable.ic_settings_print), + getBluetoothDrawable( + context, com.android.internal.R.drawable.ic_settings_print), context.getString(R.string.bluetooth_talkback_imaging)); default: @@ -125,8 +129,9 @@ public class BluetoothUtils { } if (cachedDevice.isHearingAidDevice()) { - return new Pair<>(getBluetoothDrawable(context, - com.android.internal.R.drawable.ic_bt_hearing_aid), + return new Pair<>( + getBluetoothDrawable( + context, com.android.internal.R.drawable.ic_bt_hearing_aid), context.getString(R.string.bluetooth_talkback_hearing_aids)); } @@ -138,7 +143,8 @@ public class BluetoothUtils { // The device should show hearing aid icon if it contains any hearing aid related // profiles if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) { - return new Pair<>(getBluetoothDrawable(context, profileResId), + return new Pair<>( + getBluetoothDrawable(context, profileResId), context.getString(R.string.bluetooth_talkback_hearing_aids)); } if (resId == 0) { @@ -153,42 +159,40 @@ public class BluetoothUtils { if (btClass != null) { if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) { return new Pair<>( - getBluetoothDrawable(context, - com.android.internal.R.drawable.ic_bt_headset_hfp), + getBluetoothDrawable( + context, com.android.internal.R.drawable.ic_bt_headset_hfp), context.getString(R.string.bluetooth_talkback_headset)); } if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) { return new Pair<>( - getBluetoothDrawable(context, - com.android.internal.R.drawable.ic_bt_headphones_a2dp), + getBluetoothDrawable( + context, com.android.internal.R.drawable.ic_bt_headphones_a2dp), context.getString(R.string.bluetooth_talkback_headphone)); } } return new Pair<>( - getBluetoothDrawable(context, - com.android.internal.R.drawable.ic_settings_bluetooth).mutate(), + getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth) + .mutate(), context.getString(R.string.bluetooth_talkback_bluetooth)); } - /** - * Get bluetooth drawable by {@code resId} - */ + /** Get bluetooth drawable by {@code resId} */ public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) { return context.getDrawable(resId); } - /** - * Get colorful bluetooth icon with description - */ - public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context, - CachedBluetoothDevice cachedDevice) { + /** Get colorful bluetooth icon with description */ + public static Pair<Drawable, String> getBtRainbowDrawableWithDescription( + Context context, CachedBluetoothDevice cachedDevice) { final Resources resources = context.getResources(); - final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context, - cachedDevice); + final Pair<Drawable, String> pair = + BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice); if (pair.first instanceof BitmapDrawable) { - return new Pair<>(new AdaptiveOutlineDrawable( - resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second); + return new Pair<>( + new AdaptiveOutlineDrawable( + resources, ((BitmapDrawable) pair.first).getBitmap()), + pair.second); } int hashCode; @@ -198,15 +202,12 @@ public class BluetoothUtils { hashCode = cachedDevice.getAddress().hashCode(); } - return new Pair<>(buildBtRainbowDrawable(context, - pair.first, hashCode), pair.second); + return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second); } - /** - * Build Bluetooth device icon with rainbow - */ - private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, - int hashCode) { + /** Build Bluetooth device icon with rainbow */ + private static Drawable buildBtRainbowDrawable( + Context context, Drawable drawable, int hashCode) { final Resources resources = context.getResources(); // Deal with normal headset @@ -222,38 +223,37 @@ public class BluetoothUtils { return adaptiveIcon; } - /** - * Get bluetooth icon with description - */ - public static Pair<Drawable, String> getBtDrawableWithDescription(Context context, - CachedBluetoothDevice cachedDevice) { - final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription( - context, cachedDevice); + /** Get bluetooth icon with description */ + public static Pair<Drawable, String> getBtDrawableWithDescription( + Context context, CachedBluetoothDevice cachedDevice) { + final Pair<Drawable, String> pair = + BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice); final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); - final int iconSize = context.getResources().getDimensionPixelSize( - R.dimen.bt_nearby_icon_size); + final int iconSize = + context.getResources().getDimensionPixelSize(R.dimen.bt_nearby_icon_size); final Resources resources = context.getResources(); // Deal with advanced device icon if (isAdvancedDetailsHeader(bluetoothDevice)) { - final Uri iconUri = getUriMetaData(bluetoothDevice, - BluetoothDevice.METADATA_MAIN_ICON); + final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON); if (iconUri != null) { try { - context.getContentResolver().takePersistableUriPermission(iconUri, - Intent.FLAG_GRANT_READ_URI_PERMISSION); + context.getContentResolver() + .takePersistableUriPermission( + iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } catch (SecurityException e) { Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e); } try { - final Bitmap bitmap = MediaStore.Images.Media.getBitmap( - context.getContentResolver(), iconUri); + final Bitmap bitmap = + MediaStore.Images.Media.getBitmap( + context.getContentResolver(), iconUri); if (bitmap != null) { - final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, - iconSize, false); + final Bitmap resizedBitmap = + Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); bitmap.recycle(); - return new Pair<>(new BitmapDrawable(resources, - resizedBitmap), pair.second); + return new Pair<>( + new BitmapDrawable(resources, resizedBitmap), pair.second); } } catch (IOException e) { Log.e(TAG, "Failed to get drawable for: " + iconUri, e); @@ -280,8 +280,8 @@ public class BluetoothUtils { return true; } // The metadata is for Android S - String deviceType = getStringMetaData(bluetoothDevice, - BluetoothDevice.METADATA_DEVICE_TYPE); + String deviceType = + getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET) || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH) || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT) @@ -306,8 +306,8 @@ public class BluetoothUtils { return true; } // The metadata is for Android S - String deviceType = getStringMetaData(bluetoothDevice, - BluetoothDevice.METADATA_DEVICE_TYPE); + String deviceType = + getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) { Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device "); return true; @@ -321,15 +321,15 @@ public class BluetoothUtils { * @param device Must be one of the public constants in {@link BluetoothClass.Device} * @return true if device class matches, false otherwise. */ - public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice, - int device) { + public static boolean isDeviceClassMatched( + @NonNull BluetoothDevice bluetoothDevice, int device) { final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass(); return bluetoothClass != null && bluetoothClass.getDeviceClass() == device; } private static boolean isAdvancedHeaderEnabled() { - if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, - true)) { + if (!DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) { Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false"); return false; } @@ -345,9 +345,7 @@ public class BluetoothUtils { return false; } - /** - * Create an Icon pointing to a drawable. - */ + /** Create an Icon pointing to a drawable. */ public static IconCompat createIconWithDrawable(Drawable drawable) { Bitmap bitmap; if (drawable instanceof BitmapDrawable) { @@ -355,19 +353,15 @@ public class BluetoothUtils { } else { final int width = drawable.getIntrinsicWidth(); final int height = drawable.getIntrinsicHeight(); - bitmap = createBitmap(drawable, - width > 0 ? width : 1, - height > 0 ? height : 1); + bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); } return IconCompat.createWithBitmap(bitmap); } - /** - * Build device icon with advanced outline - */ + /** Build device icon with advanced outline */ public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) { - final int iconSize = context.getResources().getDimensionPixelSize( - R.dimen.advanced_icon_size); + final int iconSize = + context.getResources().getDimensionPixelSize(R.dimen.advanced_icon_size); final Resources resources = context.getResources(); Bitmap bitmap = null; @@ -376,14 +370,12 @@ public class BluetoothUtils { } else { final int width = drawable.getIntrinsicWidth(); final int height = drawable.getIntrinsicHeight(); - bitmap = createBitmap(drawable, - width > 0 ? width : 1, - height > 0 ? height : 1); + bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); } if (bitmap != null) { - final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, - iconSize, false); + final Bitmap resizedBitmap = + Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); bitmap.recycle(); return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED); } @@ -391,9 +383,7 @@ public class BluetoothUtils { return drawable; } - /** - * Creates a drawable with specified width and height. - */ + /** Creates a drawable with specified width and height. */ public static Bitmap createBitmap(Drawable drawable, int width, int height) { final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); @@ -487,11 +477,8 @@ public class BluetoothUtils { } /** - * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: - * 1) currently connected - * 2) is Hearing Aid or LE Audio - * OR - * 3) connected profile matches currentAudioProfile + * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: 1) currently + * connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile * * @param cachedDevice the CachedBluetoothDevice * @param audioManager audio manager to get the current audio profile @@ -519,8 +506,11 @@ public class BluetoothUtils { // It would show in Available Devices group. if (cachedDevice.isConnectedAshaHearingAidDevice() || cachedDevice.isConnectedLeAudioDevice()) { - Log.d(TAG, "isFilterMatched() device : " - + cachedDevice.getName() + ", the profile is connected."); + Log.d( + TAG, + "isFilterMatched() device : " + + cachedDevice.getName() + + ", the profile is connected."); return true; } // According to the current audio profile type, @@ -541,11 +531,79 @@ public class BluetoothUtils { return isFilterMatched; } + /** Returns if the le audio sharing is enabled. */ + public static boolean isAudioSharingEnabled() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + return Flags.enableLeAudioSharing() + && adapter.isLeAudioBroadcastSourceSupported() + == BluetoothStatusCodes.FEATURE_SUPPORTED + && adapter.isLeAudioBroadcastAssistantSupported() + == BluetoothStatusCodes.FEATURE_SUPPORTED; + } + + /** Returns if the broadcast is on-going. */ + @WorkerThread + public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) { + if (manager == null) return false; + LocalBluetoothLeBroadcast broadcast = + manager.getProfileManager().getLeAudioBroadcastProfile(); + return broadcast != null && broadcast.isEnabled(null); + } + + /** + * Check if {@link CachedBluetoothDevice} has connected to a broadcast source. + * + * @param cachedDevice The cached bluetooth device to check. + * @param localBtManager The BT manager to provide BT functions. + * @return Whether the device has connected to a broadcast source. + */ + @WorkerThread + public static boolean hasConnectedBroadcastSource( + CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { + if (localBtManager == null) { + Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null"); + return false; + } + LocalBluetoothLeBroadcastAssistant assistant = + localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); + if (assistant == null) { + Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null"); + return false; + } + List<BluetoothLeBroadcastReceiveState> sourceList = + assistant.getAllSources(cachedDevice.getDevice()); + if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) { + Log.d( + TAG, + "Lead device has connected broadcast source, device = " + + cachedDevice.getDevice().getAnonymizedAddress()); + return true; + } + // Return true if member device is in broadcast. + for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { + List<BluetoothLeBroadcastReceiveState> list = + assistant.getAllSources(device.getDevice()); + if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) { + Log.d( + TAG, + "Member device has connected broadcast source, device = " + + device.getDevice().getAnonymizedAddress()); + return true; + } + } + return false; + } + + /** Checks the connectivity status based on the provided broadcast receive state. */ + @WorkerThread + public static boolean isConnected(BluetoothLeBroadcastReceiveState state) { + return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0); + } + /** - * Checks if the Bluetooth device is an available hearing device, which means: - * 1) currently connected - * 2) is Hearing Aid - * 3) connected profile match hearing aid related profiles (e.g. ASHA, HAP) + * Checks if the Bluetooth device is an available hearing device, which means: 1) currently + * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g. + * ASHA, HAP) * * @param cachedDevice the CachedBluetoothDevice * @return if the device is Available hearing device @@ -553,19 +611,20 @@ public class BluetoothUtils { @WorkerThread public static boolean isAvailableHearingDevice(CachedBluetoothDevice cachedDevice) { if (isDeviceConnected(cachedDevice) && cachedDevice.isConnectedHearingAidDevice()) { - Log.d(TAG, "isFilterMatched() device : " - + cachedDevice.getName() + ", the profile is connected."); + Log.d( + TAG, + "isFilterMatched() device : " + + cachedDevice.getName() + + ", the profile is connected."); return true; } return false; } /** - * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: - * 1) currently connected - * 2) is not Hearing Aid or LE Audio - * AND - * 3) connected profile does not match currentAudioProfile + * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: 1) currently + * connected 2) is not Hearing Aid or LE Audio AND 3) connected profile does not match + * currentAudioProfile * * @param cachedDevice the CachedBluetoothDevice * @param audioManager audio manager to get the current audio profile @@ -675,29 +734,28 @@ public class BluetoothUtils { } /** - * Returns the BluetoothDevice's exclusive manager - * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the - * given set, otherwise null. + * Returns the BluetoothDevice's exclusive manager ({@link + * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given + * set, otherwise null. */ @Nullable private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) { - byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata( - BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); + byte[] exclusiveManagerNameBytes = + bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); if (exclusiveManagerNameBytes == null) { - Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName() - + " doesn't have exclusive manager"); + Log.d( + TAG, + "Bluetooth device " + + bluetoothDevice.getName() + + " doesn't have exclusive manager"); return null; } String exclusiveManagerName = new String(exclusiveManagerNameBytes); - return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName - : null; + return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null; } - /** - * Checks if given package is installed - */ - private static boolean isPackageInstalled(Context context, - String packageName) { + /** Checks if given package is installed */ + private static boolean isPackageInstalled(Context context, String packageName) { PackageManager packageManager = context.getPackageManager(); try { packageManager.getPackageInfo(packageName, 0); @@ -709,13 +767,12 @@ public class BluetoothUtils { } /** - * A BluetoothDevice is exclusively managed if - * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. - * 2) the exclusive manager app name is in the allowlist. - * 3) the exclusive manager app is installed. + * A BluetoothDevice is exclusively managed if 1) it has field {@link + * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is + * in the allowlist. 3) the exclusive manager app is installed. */ - public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context, - @NonNull BluetoothDevice bluetoothDevice) { + public static boolean isExclusivelyManagedBluetoothDevice( + @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) { String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice); if (exclusiveManagerName == null) { return false; @@ -728,9 +785,7 @@ public class BluetoothUtils { } } - /** - * Return the allowlist for exclusive manager names. - */ + /** Return the allowlist for exclusive manager names. */ @NonNull public static Set<String> getExclusiveManagers() { return EXCLUSIVE_MANAGERS; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 1246fd85ee16..f197f9ee0baf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -17,6 +17,7 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; @@ -25,6 +26,7 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -44,6 +46,9 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.ArrayList; +import java.util.List; + @RunWith(RobolectricTestRunner.class) public class BluetoothUtilsTest { @@ -55,6 +60,16 @@ public class BluetoothUtilsTest { private AudioManager mAudioManager; @Mock private PackageManager mPackageManager; + @Mock + private LocalBluetoothLeBroadcast mBroadcast; + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private LocalBluetoothLeBroadcastAssistant mAssistant; + @Mock + private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState; private Context mContext; private static final String STRING_METADATA = "string_metadata"; @@ -72,6 +87,9 @@ public class BluetoothUtilsTest { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager); + when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); + when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); } @Test @@ -432,6 +450,30 @@ public class BluetoothUtilsTest { } @Test + public void testIsBroadcasting_broadcastEnabled_returnTrue() { + when(mBroadcast.isEnabled(any())).thenReturn(true); + assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isEqualTo(true); + } + + @Test + public void testHasConnectedBroadcastSource_deviceConnectedToBroadcastSource() { + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + + List<Long> bisSyncState = new ArrayList<>(); + bisSyncState.add(1L); + when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState); + + List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>(); + sourceList.add(mLeBroadcastReceiveState); + when(mAssistant.getAllSources(any())).thenReturn(sourceList); + + assertThat( + BluetoothUtils.hasConnectedBroadcastSource( + mCachedBluetoothDevice, mLocalBluetoothManager)) + .isEqualTo(true); + } + + @Test public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() { when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index fcd77609768e..02a12e4e0814 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -54,9 +54,9 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateElementFloatAsState -import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.settingslib.Utils import com.android.systemui.battery.BatteryMeterView @@ -87,10 +87,6 @@ object ShadeHeader { val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup") } - object Keys { - val transitionProgress = ValueKey("ShadeHeaderTransitionProgress") - } - object Values { val ClockScale = ValueKey("ShadeHeaderClockScale") } @@ -119,19 +115,17 @@ fun SceneScope.CollapsedShadeHeader( return } - val formatProgress = - animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress) - .unsafeCompositionState(initialValue = 0f) - val cutoutWidth = LocalDisplayCutout.current.width() val cutoutLocation = LocalDisplayCutout.current.location val useExpandedFormat by - remember(formatProgress) { + remember(cutoutLocation) { derivedStateOf { - cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f + cutoutLocation != CutoutLocation.CENTER || + shouldUseExpandedFormat(layoutState.transitionState) } } + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState() // This layout assumes it is globally positioned at (0, 0) and is the @@ -207,7 +201,7 @@ fun SceneScope.CollapsedShadeHeader( val screenWidth = constraints.maxWidth val cutoutWidthPx = cutoutWidth.roundToPx() - val height = ShadeHeader.Dimensions.CollapsedHeight.roundToPx() + val height = CollapsedHeight.roundToPx() val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height) val startMeasurable = measurables[0][0] @@ -261,11 +255,10 @@ fun SceneScope.ExpandedShadeHeader( return } - val formatProgress = - animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress) - .unsafeCompositionState(initialValue = 1f) - val useExpandedFormat by - remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } } + val useExpandedFormat by remember { + derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } + } + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState() Box(modifier = modifier) { @@ -530,3 +523,15 @@ private fun SceneScope.PrivacyChip( modifier = modifier.element(ShadeHeader.Elements.PrivacyChip), ) } + +private fun shouldUseExpandedFormat(state: TransitionState): Boolean { + return when (state) { + is TransitionState.Idle -> { + state.currentScene == Scenes.QuickSettings + } + is TransitionState.Transition -> { + (state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) && state.progress >= 0.5) || + (state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) && state.progress < 0.5) + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt new file mode 100644 index 000000000000..167eb65da7ee --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt @@ -0,0 +1,41 @@ +/* + * 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.volume.panel.component.button.ui.composable + +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +/** + * Container to create a rim around the button. Both `Expandable` and `OutlinedIconToggleButton` + * have antialiasing problem when used with [androidx.compose.foundation.BorderStroke] and some + * contrast container color. + */ +// TODO(b/331584069): Remove this once antialiasing bug is fixed +@Composable +fun BottomComponentButtonSurface(modifier: Modifier = Modifier, content: @Composable () -> Unit) { + Surface( + modifier = modifier.height(64.dp), + shape = RoundedCornerShape(28.dp), + color = MaterialTheme.colorScheme.surface, + content = content, + ) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt index 8f187cce9e77..fc511e12ec54 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt @@ -16,14 +16,12 @@ package com.android.systemui.volume.panel.component.button.ui.composable -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -64,20 +62,21 @@ class ButtonComponent( verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - Expandable( - modifier = - Modifier.height(64.dp).fillMaxWidth().semantics { - role = Role.Button - contentDescription = label - }, - color = MaterialTheme.colorScheme.primaryContainer, - shape = RoundedCornerShape(28.dp), - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - borderStroke = BorderStroke(8.dp, MaterialTheme.colorScheme.surface), - onClick = onClick, - ) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) + BottomComponentButtonSurface { + Expandable( + modifier = + Modifier.fillMaxSize().padding(8.dp).semantics { + role = Role.Button + contentDescription = label + }, + color = MaterialTheme.colorScheme.primaryContainer, + shape = RoundedCornerShape(28.dp), + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + onClick = onClick, + ) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) + } } } Text( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt index 51ec63b16fbb..780e3f2de4c8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt @@ -16,23 +16,23 @@ package com.android.systemui.volume.panel.component.button.ui.composable -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedIconToggleButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -62,26 +62,33 @@ class ToggleButtonComponent( verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - OutlinedIconToggleButton( - modifier = - Modifier.height(64.dp).fillMaxWidth().semantics { - role = Role.Switch - contentDescription = label - }, - checked = viewModel.isChecked, - onCheckedChange = onCheckedChange, - shape = RoundedCornerShape(28.dp), - colors = - IconButtonDefaults.outlinedIconToggleButtonColors( - containerColor = MaterialTheme.colorScheme.surface, - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - checkedContainerColor = MaterialTheme.colorScheme.primaryContainer, - checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer, - ), - border = BorderStroke(8.dp, MaterialTheme.colorScheme.surface), - ) { - Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) + BottomComponentButtonSurface { + val colors = + if (viewModel.isChecked) { + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + ) + } else { + ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Button( + modifier = + Modifier.fillMaxSize().padding(8.dp).semantics { + role = Role.Switch + contentDescription = label + }, + onClick = { onCheckedChange(!viewModel.isChecked) }, + shape = RoundedCornerShape(28.dp), + colors = colors + ) { + Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) + } } + Text( modifier = Modifier.clearAndSetSemantics {}.basicMarquee(), text = label, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt index e2d7d1165bb7..c74331477229 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt @@ -46,6 +46,12 @@ import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset @@ -112,10 +118,16 @@ fun VolumePanelRadioButtonBar( horizontalArrangement = Arrangement.spacedBy(spacing) ) { for (itemIndex in items.indices) { + val item = items[itemIndex] Row( modifier = Modifier.height(48.dp) .weight(1f) + .semantics { + item.contentDescription?.let { contentDescription = it } + role = Role.Switch + selected = itemIndex == scope.selectedIndex + } .clickable( interactionSource = null, indication = null, @@ -124,7 +136,6 @@ fun VolumePanelRadioButtonBar( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { - val item = items[itemIndex] if (item.icon !== Empty) { with(items[itemIndex]) { icon() } } @@ -138,7 +149,8 @@ fun VolumePanelRadioButtonBar( start = indicatorBackgroundPadding, top = labelIndicatorBackgroundSpacing, end = indicatorBackgroundPadding - ), + ) + .clearAndSetSemantics {}, horizontalArrangement = Arrangement.spacedBy(spacing), ) { for (itemIndex in items.indices) { @@ -296,6 +308,7 @@ interface VolumePanelRadioButtonBarScope { onItemSelected: () -> Unit, icon: @Composable RowScope.() -> Unit = Empty, label: @Composable RowScope.() -> Unit = Empty, + contentDescription: String? = null, ) } @@ -317,6 +330,7 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop onItemSelected: () -> Unit, icon: @Composable RowScope.() -> Unit, label: @Composable RowScope.() -> Unit, + contentDescription: String?, ) { require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" } if (isSelected) { @@ -327,6 +341,7 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop onItemSelected = onItemSelected, icon = icon, label = label, + contentDescription = contentDescription, ) ) } @@ -340,6 +355,7 @@ private class Item( val onItemSelected: () -> Unit, val icon: @Composable RowScope.() -> Unit, val label: @Composable RowScope.() -> Unit, + val contentDescription: String?, ) private const val UNSET_OFFSET = -1 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt index 6673afdfafb4..eed54dab6faf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt @@ -74,9 +74,11 @@ constructor( } VolumePanelRadioButtonBar { for (buttonViewModel in enabledModelStates) { + val label = buttonViewModel.button.label.toString() item( isSelected = buttonViewModel.button.isChecked, onItemSelected = { viewModel.setEnabled(buttonViewModel.model) }, + contentDescription = label, icon = { Icon( icon = buttonViewModel.button.icon, @@ -86,7 +88,7 @@ constructor( label = { Text( modifier = Modifier.basicMarquee(), - text = buttonViewModel.button.label.toString(), + text = label, style = MaterialTheme.typography.labelMedium, color = buttonViewModel.labelColor.toColor(), textAlign = TextAlign.Center, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 96eb4b3a4c3f..475bb2c83b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -43,14 +43,14 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory; +import com.android.systemui.bluetooth.qsdialog.AvailableHearingDeviceItemFactory; +import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory; +import com.android.systemui.bluetooth.qsdialog.DeviceItem; +import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory; +import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.qs.tiles.dialog.bluetooth.ActiveHearingDeviceItemFactory; -import com.android.systemui.qs.tiles.dialog.bluetooth.AvailableHearingDeviceItemFactory; -import com.android.systemui.qs.tiles.dialog.bluetooth.ConnectedDeviceItemFactory; -import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem; -import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemFactory; -import com.android.systemui.qs.tiles.dialog.bluetooth.SavedHearingDeviceItemFactory; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java index 695d04f5df9d..737805b4d62f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java @@ -27,7 +27,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem; +import com.android.systemui.bluetooth.qsdialog.DeviceItem; import com.android.systemui.res.R; import kotlin.Pair; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt index 59fc81c82df0..f86cad5b9c59 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.util.Log import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt index 9ee582a77862..81fe2a5a5f0d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.util.Log diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt index 9c63a30dfc1c..94d7af74f1dd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.STATE_OFF diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index a8d9e781228b..c7d171d5b804 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.os.Bundle import android.view.LayoutInflater diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt index 5d18dc1d453a..c30aea07e959 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.DEBUG diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt index ea51beecc2c1..6e51915797cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import com.android.settingslib.bluetooth.CachedBluetoothDevice diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt index cd52e0dcca4a..add1647143d8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt index fd624d2f1ba1..e65b65710f94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.content.Intent import android.content.SharedPreferences @@ -31,15 +31,15 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.Prefs import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt index 1c621b87533d..dc5efefdfb16 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt @@ -30,7 +30,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.graphics.drawable.Drawable import com.android.settingslib.bluetooth.CachedBluetoothDevice diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt index 56ba07941e4d..f04ba75ca3ef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothDevice import android.content.Context diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt index fce25ec68190..4e28cafb5004 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 42bd4aff1dc4..ce1aed08ab49 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -621,7 +621,9 @@ constructor( } private suspend fun updateClockAppearance(clock: ClockController) { - clockController.clock = clock + if (!MigrateClocksToBlueprint.isEnabled) { + clockController.clock = clock + } val colors = wallpaperColors if (clockRegistry.seedColor == null && colors != null) { // Seed color null means users do not override any color on the clock. The default @@ -639,6 +641,11 @@ constructor( if (isWallpaperDark) lightClockColor else darkClockColor ) } + // In clock preview, we should have a seed color for clock + // before setting clock to clockEventController to avoid updateColor with seedColor == null + if (MigrateClocksToBlueprint.isEnabled) { + clockController.clock = clock + } } private fun onClockChanged() { if (MigrateClocksToBlueprint.isEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index d2471225a093..f08bc17c4f23 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -25,8 +25,8 @@ import com.android.launcher3.icons.IconFactory import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader -import com.android.systemui.mediaprojection.appselector.data.AppIconLoader -import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader +import com.android.systemui.mediaprojection.appselector.data.BasicAppIconLoader +import com.android.systemui.mediaprojection.appselector.data.BasicPackageManagerAppIconLoader import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader @@ -102,7 +102,7 @@ interface MediaProjectionAppSelectorModule { @Binds @MediaProjectionAppSelectorScope - fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader + fun bindAppIconLoader(impl: BasicPackageManagerAppIconLoader): BasicAppIconLoader @Binds @IntoSet diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt new file mode 100644 index 000000000000..ca5b5f842b25 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt @@ -0,0 +1,68 @@ +/* + * 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.mediaprojection.appselector.data + +import android.content.ComponentName +import android.content.Context +import android.graphics.drawable.Drawable +import android.os.UserHandle +import com.android.launcher3.icons.BaseIconFactory +import com.android.launcher3.icons.IconFactory +import com.android.launcher3.util.UserIconInfo +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import javax.inject.Provider +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +class BadgedAppIconLoader +@Inject +constructor( + private val basicAppIconLoader: BasicAppIconLoader, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val context: Context, + private val iconFactoryProvider: Provider<IconFactory>, +) { + + suspend fun loadIcon( + userId: Int, + userType: RecentTask.UserType, + componentName: ComponentName + ): Drawable? = + withContext(backgroundDispatcher) { + iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory -> + val icon = + basicAppIconLoader.loadIcon(userId, componentName) ?: return@withContext null + val userHandler = UserHandle.of(userId) + val iconType = getIconType(userType) + val options = + BaseIconFactory.IconOptions().apply { + setUser(UserIconInfo(userHandler, iconType)) + } + val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options) + badgedIcon.newIcon(context) + } + } + + private fun getIconType(userType: RecentTask.UserType): Int = + when (userType) { + RecentTask.UserType.CLONED -> UserIconInfo.TYPE_CLONED + RecentTask.UserType.WORK -> UserIconInfo.TYPE_WORK + RecentTask.UserType.PRIVATE -> UserIconInfo.TYPE_PRIVATE + RecentTask.UserType.STANDARD -> UserIconInfo.TYPE_MAIN + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt index b85d6285c35b..03f6f015300f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt @@ -17,45 +17,29 @@ package com.android.systemui.mediaprojection.appselector.data import android.content.ComponentName -import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import android.os.UserHandle -import com.android.launcher3.icons.BaseIconFactory.IconOptions -import com.android.launcher3.icons.IconFactory import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.shared.system.PackageManagerWrapper import javax.inject.Inject -import javax.inject.Provider import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext -interface AppIconLoader { +interface BasicAppIconLoader { suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? } -class IconLoaderLibAppIconLoader +class BasicPackageManagerAppIconLoader @Inject constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, - private val context: Context, // Use wrapper to access hidden API that allows to get ActivityInfo for any user id private val packageManagerWrapper: PackageManagerWrapper, private val packageManager: PackageManager, - private val iconFactoryProvider: Provider<IconFactory> -) : AppIconLoader { +) : BasicAppIconLoader { override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? = withContext(backgroundDispatcher) { - iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory -> - val activityInfo = - packageManagerWrapper.getActivityInfo(component, userId) - ?: return@withContext null - val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null - val userHandler = UserHandle.of(userId) - val options = IconOptions().apply { setUser(userHandler) } - val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options) - badgedIcon.newIcon(context) - } + packageManagerWrapper.getActivityInfo(component, userId)?.loadIcon(packageManager) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index e9b458271ef7..3e9b546d58c9 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -28,4 +28,12 @@ data class RecentTask( val baseIntentComponent: ComponentName?, @ColorInt val colorBackground: Int?, val isForegroundTask: Boolean, -) + val userType: UserType, +) { + enum class UserType { + STANDARD, + WORK, + PRIVATE, + CLONED + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 5dde14bf0867..a6049c8b556d 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -17,6 +17,8 @@ package com.android.systemui.mediaprojection.appselector.data import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE +import android.content.pm.UserInfo +import android.os.UserManager import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker import com.android.systemui.util.kotlin.getOrNull @@ -41,7 +43,8 @@ constructor( @Background private val coroutineDispatcher: CoroutineDispatcher, @Background private val backgroundExecutor: Executor, private val recentTasks: Optional<RecentTasks>, - private val userTracker: UserTracker + private val userTracker: UserTracker, + private val userManager: UserManager, ) : RecentTaskListProvider { private val recents by lazy { recentTasks.getOrNull() } @@ -65,7 +68,8 @@ constructor( it.topActivity, it.baseIntent?.component, it.taskDescription?.backgroundColor, - isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible + isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible, + userType = userManager.getUserInfo(it.userId).toUserType(), ) } } @@ -81,4 +85,15 @@ constructor( continuation.resume(tasks) } } + + private fun UserInfo.toUserType(): RecentTask.UserType = + if (isCloneProfile) { + RecentTask.UserType.CLONED + } else if (isManagedProfile) { + RecentTask.UserType.WORK + } else if (isPrivateProfile) { + RecentTask.UserType.PRIVATE + } else { + RecentTask.UserType.STANDARD + } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt index 3fe040a0d715..3b84d2c53a2b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt @@ -21,12 +21,12 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView.ViewHolder -import com.android.systemui.res.R import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector -import com.android.systemui.mediaprojection.appselector.data.AppIconLoader +import com.android.systemui.mediaprojection.appselector.data.BadgedAppIconLoader import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -39,7 +39,7 @@ class RecentTaskViewHolder @AssistedInject constructor( @Assisted private val root: ViewGroup, - private val iconLoader: AppIconLoader, + private val iconLoader: BadgedAppIconLoader, private val thumbnailLoader: RecentTaskThumbnailLoader, private val labelLoader: RecentTaskLabelLoader, private val taskViewSizeProvider: TaskPreviewSizeProvider, @@ -63,7 +63,7 @@ constructor( scope.launch { task.baseIntentComponent?.let { component -> launch { - val icon = iconLoader.loadIcon(task.userId, component) + val icon = iconLoader.loadIcon(task.userId, task.userType, component) iconView.setImageDrawable(icon) } launch { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index b0707db0d02d..6eae32a0ffd6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -39,6 +39,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -51,7 +52,6 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index d00081e0f595..1568e8c011a1 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -20,6 +20,9 @@ import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; import static android.app.WallpaperManager.SetWallpaperFlags; +import static com.android.window.flags.Flags.offloadColorExtraction; + +import android.annotation.Nullable; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.Context; @@ -135,6 +138,12 @@ public class ImageWallpaper extends WallpaperService { mLongExecutor, mLock, new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() { + + @Override + public void onColorsProcessed() { + CanvasEngine.this.notifyColorsChanged(); + } + @Override public void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) { @@ -433,6 +442,12 @@ public class ImageWallpaper extends WallpaperService { } @Override + public @Nullable WallpaperColors onComputeColors() { + if (!offloadColorExtraction()) return null; + return mWallpaperLocalColorExtractor.onComputeColors(); + } + + @Override public boolean supportsLocalColorExtraction() { return true; } @@ -469,6 +484,12 @@ public class ImageWallpaper extends WallpaperService { } @Override + public void onDimAmountChanged(float dimAmount) { + if (!offloadColorExtraction()) return; + mWallpaperLocalColorExtractor.onDimAmountChanged(dimAmount); + } + + @Override public void onDisplayAdded(int displayId) { } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java index e2ec8dc056ca..d37dfb49c5e5 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java @@ -17,6 +17,8 @@ package com.android.systemui.wallpapers; +import static com.android.window.flags.Flags.offloadColorExtraction; + import android.app.WallpaperColors; import android.graphics.Bitmap; import android.graphics.Rect; @@ -66,6 +68,12 @@ public class WallpaperLocalColorExtractor { private final List<RectF> mPendingRegions = new ArrayList<>(); private final Set<RectF> mProcessedRegions = new ArraySet<>(); + private float mWallpaperDimAmount = 0f; + private WallpaperColors mWallpaperColors; + + // By default we assume that colors were loaded from disk and don't need to be recomputed + private boolean mRecomputeColors = false; + @LongRunning private final Executor mLongExecutor; @@ -75,6 +83,12 @@ public class WallpaperLocalColorExtractor { * Interface to handle the callbacks after the different steps of the color extraction */ public interface WallpaperLocalColorExtractorCallback { + + /** + * Callback after the wallpaper colors have been computed + */ + void onColorsProcessed(); + /** * Callback after the colors of new regions have been extracted * @param regions the list of new regions that have been processed @@ -129,7 +143,7 @@ public class WallpaperLocalColorExtractor { if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return; mDisplayWidth = displayWidth; mDisplayHeight = displayHeight; - processColorsInternal(); + processLocalColorsInternal(); } } @@ -166,7 +180,8 @@ public class WallpaperLocalColorExtractor { mBitmapHeight = bitmap.getHeight(); mMiniBitmap = createMiniBitmap(bitmap); mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated(); - recomputeColors(); + if (offloadColorExtraction() && mRecomputeColors) recomputeColorsInternal(); + recomputeLocalColors(); } } @@ -184,16 +199,66 @@ public class WallpaperLocalColorExtractor { if (mPages == pages) return; mPages = pages; if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) { - recomputeColors(); + recomputeLocalColors(); } } } - // helper to recompute colors, to be called in synchronized methods - private void recomputeColors() { + /** + * Should be called when the dim amount of the wallpaper changes, to recompute the colors + */ + public void onDimAmountChanged(float dimAmount) { + mLongExecutor.execute(() -> onDimAmountChangedSynchronized(dimAmount)); + } + + private void onDimAmountChangedSynchronized(float dimAmount) { + synchronized (mLock) { + if (mWallpaperDimAmount == dimAmount) return; + mWallpaperDimAmount = dimAmount; + mRecomputeColors = true; + recomputeColorsInternal(); + } + } + + /** + * To be called by {@link ImageWallpaper.CanvasEngine#onComputeColors}. This will either + * return the current wallpaper colors, or if the bitmap is not yet loaded, return null and call + * {@link WallpaperLocalColorExtractorCallback#onColorsProcessed()} when the colors are ready. + */ + public WallpaperColors onComputeColors() { + mLongExecutor.execute(this::onComputeColorsSynchronized); + return mWallpaperColors; + } + + private void onComputeColorsSynchronized() { + synchronized (mLock) { + if (mRecomputeColors) return; + mRecomputeColors = true; + recomputeColorsInternal(); + } + } + + /** + * helper to recompute main colors, to be called in synchronized methods + */ + private void recomputeColorsInternal() { + if (mMiniBitmap == null) return; + mWallpaperColors = getWallpaperColors(mMiniBitmap, mWallpaperDimAmount); + mWallpaperLocalColorExtractorCallback.onColorsProcessed(); + } + + @VisibleForTesting + WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap, float dimAmount) { + return WallpaperColors.fromBitmap(bitmap, dimAmount); + } + + /** + * helper to recompute local colors, to be called in synchronized methods + */ + private void recomputeLocalColors() { mPendingRegions.addAll(mProcessedRegions); mProcessedRegions.clear(); - processColorsInternal(); + processLocalColorsInternal(); } /** @@ -216,7 +281,7 @@ public class WallpaperLocalColorExtractor { if (!wasActive && isActive()) { mWallpaperLocalColorExtractorCallback.onActivated(); } - processColorsInternal(); + processLocalColorsInternal(); } } @@ -353,7 +418,7 @@ public class WallpaperLocalColorExtractor { * then notify the callback with the resulting colors for these regions * This method should only be called synchronously */ - private void processColorsInternal() { + private void processLocalColorsInternal() { /* * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been * called, and thus the wallpaper is not yet loaded. In that case, exit, the function diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index b0f0363a3e97..15764571ce02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -43,10 +43,10 @@ import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.bluetooth.qsdialog.DeviceItem; +import com.android.systemui.bluetooth.qsdialog.DeviceItemType; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem; -import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemType; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.phone.SystemUIDialogManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java index 95d5597ef645..d16db65334d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java @@ -27,7 +27,7 @@ import androidx.test.filters.SmallTest; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.SysuiTestCase; -import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem; +import com.android.systemui.bluetooth.qsdialog.DeviceItem; import org.junit.Before; import org.junit.Rule; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt index 036d3c862ae0..4949716ad129 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.testing.AndroidTestingRunner diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt index 31192841ec77..85e2a8d4b48e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.testing.AndroidTestingRunner diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt index 479e62d4d724..a8f82eda51c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.testing.AndroidTestingRunner import android.testing.TestableLooper diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt index 17b612714fe2..12dfe97649d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog -import android.content.Context import android.graphics.drawable.Drawable import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -121,23 +120,18 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { sysuiDialogFactory ) - whenever( - sysuiDialogFactory.create( - any(SystemUIDialog.Delegate::class.java) - ) + whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer { + SystemUIDialog( + mContext, + 0, + SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK, + dialogManager, + sysuiState, + fakeBroadcastDispatcher, + dialogTransitionAnimator, + it.getArgument(0) ) - .thenAnswer { - SystemUIDialog( - mContext, - 0, - SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK, - dialogManager, - sysuiState, - fakeBroadcastDispatcher, - dialogTransitionAnimator, - it.getArgument(0) - ) - } + } icon = Pair(drawable, DEVICE_NAME) deviceItem = @@ -163,6 +157,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { assertThat(recyclerView.visibility).isEqualTo(VISIBLE) assertThat(recyclerView.adapter).isNotNull() assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue() + dialog.dismiss() } @Test @@ -184,6 +179,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME) assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY) assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon) + dialog.dismiss() } } @@ -259,6 +255,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { assertThat(pairNewButton.visibility).isEqualTo(VISIBLE) assertThat(adapter.itemCount).isEqualTo(1) assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT) + dialog.dismiss() } } @@ -283,6 +280,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog.show() assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height) .isEqualTo(cachedHeight) + dialog.dismiss() } } @@ -306,6 +304,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog.show() assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height) .isGreaterThan(MATCH_PARENT) + dialog.dismiss() } } @@ -331,6 +330,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility ) .isEqualTo(GONE) + dialog.dismiss() } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt index da8f60a9b926..4aa6209fab3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.testing.AndroidTestingRunner diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt index c8a2aa64ffa2..6d99c5b62e9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.testing.AndroidTestingRunner diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt index a8cd8c801a95..28cbcb435223 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothDevice import android.content.pm.PackageInfo diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt index ddf0b9a78165..eb735cbfec47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.dialog.bluetooth +package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 44798ea99bee..8b79fa45b8ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -257,6 +257,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { userId = userId, colorBackground = 0, isForegroundTask = isForegroundTask, + userType = RecentTask.UserType.STANDARD, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt index 9b346d0120ef..fa1c8f8ea2c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt @@ -20,15 +20,10 @@ import android.content.ComponentName import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.graphics.Bitmap -import android.graphics.drawable.Drawable import androidx.test.filters.SmallTest -import com.android.launcher3.icons.BitmapInfo import com.android.launcher3.icons.FastBitmapDrawable -import com.android.launcher3.icons.IconFactory import com.android.systemui.SysuiTestCase import com.android.systemui.shared.system.PackageManagerWrapper -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -40,20 +35,17 @@ import org.junit.runners.JUnit4 @SmallTest @RunWith(JUnit4::class) -class IconLoaderLibAppIconLoaderTest : SysuiTestCase() { +class BasicPackageManagerAppIconLoaderTest : SysuiTestCase() { - private val iconFactory: IconFactory = mock() private val packageManagerWrapper: PackageManagerWrapper = mock() private val packageManager: PackageManager = mock() private val dispatcher = Dispatchers.Unconfined private val appIconLoader = - IconLoaderLibAppIconLoader( + BasicPackageManagerAppIconLoader( backgroundDispatcher = dispatcher, - context = context, packageManagerWrapper = packageManagerWrapper, packageManager = packageManager, - iconFactoryProvider = { iconFactory } ) @Test @@ -70,12 +62,7 @@ class IconLoaderLibAppIconLoaderTest : SysuiTestCase() { private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) { val activityInfo = mock<ActivityInfo>() whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo) - val rawIcon = mock<Drawable>() - whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon) - - val bitmapInfo = mock<BitmapInfo>() - whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo) - whenever(bitmapInfo.newIcon(context)).thenReturn(icon) + whenever(activityInfo.loadIcon(packageManager)).thenReturn(icon) } private fun createIcon(): FastBitmapDrawable = diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index b593def283ae..dd621129ad9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -1,9 +1,15 @@ package com.android.systemui.mediaprojection.appselector.data import android.app.ActivityManager.RecentTaskInfo +import android.content.pm.UserInfo +import android.os.UserManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.CLONED +import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.PRIVATE +import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.STANDARD +import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.WORK import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -17,6 +23,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt @RunWith(AndroidTestingRunner::class) @SmallTest @@ -25,12 +32,16 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { private val dispatcher = Dispatchers.Unconfined private val recentTasks: RecentTasks = mock() private val userTracker: UserTracker = mock() + private val userManager: UserManager = mock { + whenever(getUserInfo(anyInt())).thenReturn(mock()) + } private val recentTaskListProvider = ShellRecentTaskListProvider( dispatcher, Runnable::run, Optional.of(recentTasks), - userTracker + userTracker, + userManager, ) @Test @@ -147,6 +158,22 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { .inOrder() } + @Test + fun loadRecentTasks_assignsCorrectUserType() { + givenRecentTasks( + createSingleTask(taskId = 1, userId = 10, userType = STANDARD), + createSingleTask(taskId = 2, userId = 20, userType = WORK), + createSingleTask(taskId = 3, userId = 30, userType = CLONED), + createSingleTask(taskId = 4, userId = 40, userType = PRIVATE), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.userType }) + .containsExactly(STANDARD, WORK, CLONED, PRIVATE) + .inOrder() + } + @Suppress("UNCHECKED_CAST") private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) { whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer { @@ -155,7 +182,10 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { } } - private fun createRecentTask(taskId: Int): RecentTask = + private fun createRecentTask( + taskId: Int, + userType: RecentTask.UserType = STANDARD + ): RecentTask = RecentTask( taskId = taskId, displayId = 0, @@ -164,25 +194,42 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { baseIntentComponent = null, colorBackground = null, isForegroundTask = false, + userType = userType, ) - private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo = - GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible)) + private fun createSingleTask( + taskId: Int, + userId: Int = 0, + isVisible: Boolean = false, + userType: RecentTask.UserType = STANDARD, + ): GroupedRecentTaskInfo { + val userInfo = + mock<UserInfo> { + whenever(isCloneProfile).thenReturn(userType == CLONED) + whenever(isManagedProfile).thenReturn(userType == WORK) + whenever(isPrivateProfile).thenReturn(userType == PRIVATE) + } + whenever(userManager.getUserInfo(userId)).thenReturn(userInfo) + return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible)) + } private fun createTaskPair( taskId1: Int, + userId1: Int = 0, taskId2: Int, + userId2: Int = 0, isVisible: Boolean = false ): GroupedRecentTaskInfo = GroupedRecentTaskInfo.forSplitTasks( - createTaskInfo(taskId1, isVisible), - createTaskInfo(taskId2, isVisible), + createTaskInfo(taskId1, userId1, isVisible), + createTaskInfo(taskId2, userId2, isVisible), null ) - private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) = + private fun createTaskInfo(taskId: Int, userId: Int, isVisible: Boolean = false) = RecentTaskInfo().apply { this.taskId = taskId this.isVisible = isVisible + this.userId = userId } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt index ac4107359dbc..a84008b0353c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt @@ -56,7 +56,8 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() { topActivityComponent = null, baseIntentComponent = null, colorBackground = null, - isForegroundTask = false + isForegroundTask = false, + userType = RecentTask.UserType.STANDARD, ) private val taskView = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 82ee99a29427..830f08a0c445 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -23,7 +23,7 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel import com.android.systemui.statusbar.policy.BluetoothController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java index 1125d41856c6..0eba21ada789 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java @@ -16,9 +16,12 @@ package com.android.systemui.wallpapers; +import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; @@ -32,6 +35,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -77,6 +81,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase { private Executor mBackgroundExecutor; private int mColorsProcessed; + private int mLocalColorsProcessed; private int mMiniBitmapUpdatedCount; private int mActivatedCount; private int mDeactivatedCount; @@ -93,6 +98,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase { private void resetCounters() { mColorsProcessed = 0; + mLocalColorsProcessed = 0; mMiniBitmapUpdatedCount = 0; mActivatedCount = 0; mDeactivatedCount = 0; @@ -112,10 +118,14 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase { new Object(), new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() { @Override + public void onColorsProcessed() { + mColorsProcessed++; + } + @Override public void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) { assertThat(regions.size()).isEqualTo(colors.size()); - mColorsProcessed += regions.size(); + mLocalColorsProcessed += regions.size(); } @Override @@ -148,8 +158,10 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase { .when(spyColorExtractor) .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt()); - doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0))) - .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class)); + WallpaperColors colors = new WallpaperColors( + Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)); + doReturn(colors).when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class)); + doReturn(colors).when(spyColorExtractor).getWallpaperColors(any(Bitmap.class), anyFloat()); return spyColorExtractor; } @@ -244,7 +256,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase { assertThat(mActivatedCount).isEqualTo(1); assertThat(mMiniBitmapUpdatedCount).isEqualTo(1); - assertThat(mColorsProcessed).isEqualTo(regions.size()); + assertThat(mLocalColorsProcessed).isEqualTo(regions.size()); spyColorExtractor.removeLocalColorAreas(regions); assertThat(mDeactivatedCount).isEqualTo(1); @@ -329,12 +341,69 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase { spyColorExtractor.onBitmapChanged(newBitmap); assertThat(mMiniBitmapUpdatedCount).isEqualTo(1); } - assertThat(mColorsProcessed).isEqualTo(regions.size()); + assertThat(mLocalColorsProcessed).isEqualTo(regions.size()); } spyColorExtractor.removeLocalColorAreas(regions); assertThat(mDeactivatedCount).isEqualTo(1); } + /** + * Test that after the bitmap changes, the colors are computed only if asked via onComputeColors + */ + @Test + @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION) + public void testRecomputeColors() { + resetCounters(); + Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT); + WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor(); + spyColorExtractor.onBitmapChanged(bitmap); + assertThat(mColorsProcessed).isEqualTo(0); + spyColorExtractor.onComputeColors(); + assertThat(mColorsProcessed).isEqualTo(1); + } + + /** + * Test that after onComputeColors is called, the colors are computed once the bitmap is loaded + */ + @Test + @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION) + public void testRecomputeColorsBeforeBitmapLoaded() { + resetCounters(); + Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT); + WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor(); + spyColorExtractor.onComputeColors(); + spyColorExtractor.onBitmapChanged(bitmap); + assertThat(mColorsProcessed).isEqualTo(1); + } + + /** + * Test that after the dim changes, the colors are computed if the bitmap is already loaded + */ + @Test + @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION) + public void testRecomputeColorsOnDimChanged() { + resetCounters(); + Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT); + WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor(); + spyColorExtractor.onBitmapChanged(bitmap); + spyColorExtractor.onDimAmountChanged(0.5f); + assertThat(mColorsProcessed).isEqualTo(1); + } + + /** + * Test that after the dim changes, the colors will be recomputed once the bitmap is loaded + */ + @Test + @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION) + public void testRecomputeColorsOnDimChangedBeforeBitmapLoaded() { + resetCounters(); + Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT); + WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor(); + spyColorExtractor.onDimAmountChanged(0.3f); + spyColorExtractor.onBitmapChanged(bitmap); + assertThat(mColorsProcessed).isEqualTo(1); + } + @Test public void testCleanUp() { resetCounters(); @@ -346,6 +415,6 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase { assertThat(mMiniBitmapUpdatedCount).isEqualTo(1); spyColorExtractor.cleanUp(); spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS)); - assertThat(mColorsProcessed).isEqualTo(0); + assertThat(mLocalColorsProcessed).isEqualTo(0); } } diff --git a/services/core/Android.bp b/services/core/Android.bp index c7d99424f72b..fd7e4abc6d16 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -254,6 +254,7 @@ java_library_static { "net_flags_lib", "stats_flags_lib", "core_os_flags_lib", + "connectivity_flags_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b6148e83d496..ad15ea90c45c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -725,6 +725,9 @@ public class ActivityManagerService extends IActivityManager.Stub // Whether we should use SCHED_FIFO for UI and RenderThreads. final boolean mUseFifoUiScheduling; + /** Whether some specified important processes are allowed to use FIFO priority. */ + boolean mAllowSpecifiedFifoScheduling = true; + @GuardedBy("this") private final SparseArray<IUnsafeIntentStrictModeCallback> mStrictModeCallbacks = new SparseArray<>(); @@ -1048,6 +1051,10 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>(); + /** The processes that are allowed to use SCHED_FIFO prorioty. */ + @GuardedBy("mProcLock") + final ArrayList<ProcessRecord> mSpecifiedFifoProcesses = new ArrayList<>(); + /** * List of records for processes that someone had tried to start before the * system was ready. We don't start them at that point, but ensure they @@ -8244,6 +8251,27 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } + /** + * Switches the priority between SCHED_FIFO and SCHED_OTHER for the main thread and render + * thread of the given process. + */ + @GuardedBy("mProcLock") + static void setFifoPriority(@NonNull ProcessRecord app, boolean enable) { + final int pid = app.getPid(); + final int renderThreadTid = app.getRenderThreadTid(); + if (enable) { + scheduleAsFifoPriority(pid, true /* suppressLogs */); + if (renderThreadTid != 0) { + scheduleAsFifoPriority(renderThreadTid, true /* suppressLogs */); + } + } else { + scheduleAsRegularPriority(pid, true /* suppressLogs */); + if (renderThreadTid != 0) { + scheduleAsRegularPriority(renderThreadTid, true /* suppressLogs */); + } + } + } + @Override public void setRenderThread(int tid) { synchronized (mProcLock) { @@ -8269,7 +8297,7 @@ public class ActivityManagerService extends IActivityManager.Stub // promote to FIFO now if (proc.mState.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) { if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band"); - if (mUseFifoUiScheduling) { + if (proc.useFifoUiScheduling()) { setThreadScheduler(proc.getRenderThreadTid(), SCHED_FIFO | SCHED_RESET_ON_FORK, 1); } else { @@ -11306,6 +11334,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (mAlwaysFinishActivities) { pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities); } + if (mAllowSpecifiedFifoScheduling) { + pw.println(" mAllowSpecifiedFifoScheduling=true"); + } if (dumpAll) { pw.println(" Total persistent processes: " + numPers); pw.println(" mProcessesReady=" + mProcessesReady @@ -17374,6 +17405,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + if (com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()) { + synchronized (mProcLock) { + adjustFifoProcessesIfNeeded(uid, !active /* allowFifo */); + } + } } final boolean isCameraActiveForUid(@UserIdInt int uid) { @@ -17382,6 +17419,34 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * This is called when the given uid is using camera. If the uid has top process state, then + * cancel the FIFO priority of the high priority processes. + */ + @VisibleForTesting + @GuardedBy("mProcLock") + void adjustFifoProcessesIfNeeded(int preemptiveUid, boolean allowSpecifiedFifo) { + if (allowSpecifiedFifo == mAllowSpecifiedFifoScheduling) { + return; + } + if (!allowSpecifiedFifo) { + final UidRecord uidRec = mProcessList.mActiveUids.get(preemptiveUid); + if (uidRec == null || uidRec.getCurProcState() > PROCESS_STATE_TOP) { + // To avoid frequent switching by background camera usages, e.g. face unlock, + // face detection (auto rotation), screen attention (keep screen on). + return; + } + } + mAllowSpecifiedFifoScheduling = allowSpecifiedFifo; + for (int i = mSpecifiedFifoProcesses.size() - 1; i >= 0; i--) { + final ProcessRecord proc = mSpecifiedFifoProcesses.get(i); + if (proc.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_TOP_APP) { + continue; + } + setFifoPriority(proc, allowSpecifiedFifo /* enable */); + } + } + @GuardedBy("this") final void doStopUidLocked(int uid, final UidRecord uidRec) { mServices.stopInBackgroundLocked(uid); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 5a750c2ba6c8..ea7a21dd19cd 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -72,7 +72,6 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYB import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL; import static android.media.audio.Flags.roForegroundAudioControl; -import static android.os.Process.SCHED_OTHER; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; import static android.os.Process.THREAD_GROUP_RESTRICTED; @@ -81,7 +80,6 @@ import static android.os.Process.THREAD_PRIORITY_DISPLAY; import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; import static android.os.Process.setProcessGroup; import static android.os.Process.setThreadPriority; -import static android.os.Process.setThreadScheduler; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; @@ -3315,22 +3313,10 @@ public class OomAdjuster { // do nothing if we already switched to RT if (oldSchedGroup != SCHED_GROUP_TOP_APP) { app.getWindowProcessController().onTopProcChanged(); - if (mService.mUseFifoUiScheduling) { + if (app.useFifoUiScheduling()) { // Switch UI pipeline for app to SCHED_FIFO state.setSavedPriority(Process.getThreadPriority(app.getPid())); - mService.scheduleAsFifoPriority(app.getPid(), true); - if (renderThreadTid != 0) { - mService.scheduleAsFifoPriority(renderThreadTid, - /* suppressLogs */true); - if (DEBUG_OOM_ADJ) { - Slog.d("UI_FIFO", "Set RenderThread (TID " + - renderThreadTid + ") to FIFO"); - } - } else { - if (DEBUG_OOM_ADJ) { - Slog.d("UI_FIFO", "Not setting RenderThread TID"); - } - } + ActivityManagerService.setFifoPriority(app, true /* enable */); } else { // Boost priority for top app UI and render threads setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); @@ -3347,22 +3333,10 @@ public class OomAdjuster { } else if (oldSchedGroup == SCHED_GROUP_TOP_APP && curSchedGroup != SCHED_GROUP_TOP_APP) { app.getWindowProcessController().onTopProcChanged(); - if (mService.mUseFifoUiScheduling) { - try { - // Reset UI pipeline to SCHED_OTHER - setThreadScheduler(app.getPid(), SCHED_OTHER, 0); - setThreadPriority(app.getPid(), state.getSavedPriority()); - if (renderThreadTid != 0) { - setThreadScheduler(renderThreadTid, - SCHED_OTHER, 0); - } - } catch (IllegalArgumentException e) { - Slog.w(TAG, - "Failed to set scheduling policy, thread does not exist:\n" - + e); - } catch (SecurityException e) { - Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e); - } + if (app.useFifoUiScheduling()) { + // Reset UI pipeline to SCHED_OTHER + ActivityManagerService.setFifoPriority(app, false /* enable */); + setThreadPriority(app.getPid(), state.getSavedPriority()); } else { // Reset priority for top app UI and render threads setThreadPriority(app.getPid(), 0); @@ -3557,7 +3531,7 @@ public class OomAdjuster { // {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it // is not ready when attaching. app.getWindowProcessController().onTopProcChanged(); - if (mService.mUseFifoUiScheduling) { + if (app.useFifoUiScheduling()) { mService.scheduleAsFifoPriority(app.getPid(), true); } else { setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index b93908974a42..08165275757e 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -745,6 +745,9 @@ class ProcessRecord implements WindowProcessListener { mOnewayThread = thread; } mWindowProcessController.setThread(thread); + if (mWindowProcessController.useFifoUiScheduling()) { + mService.mSpecifiedFifoProcesses.add(this); + } } @GuardedBy({"mService", "mProcLock"}) @@ -752,9 +755,19 @@ class ProcessRecord implements WindowProcessListener { mThread = null; mOnewayThread = null; mWindowProcessController.setThread(null); + if (mWindowProcessController.useFifoUiScheduling()) { + mService.mSpecifiedFifoProcesses.remove(this); + } mProfile.onProcessInactive(tracker); } + @GuardedBy(anyOf = {"mService", "mProcLock"}) + boolean useFifoUiScheduling() { + return mService.mUseFifoUiScheduling + || (mService.mAllowSpecifiedFifoScheduling + && mWindowProcessController.useFifoUiScheduling()); + } + @GuardedBy("mService") int getDyingPid() { return mDyingPid; diff --git a/services/core/java/com/android/server/connectivity/Android.bp b/services/core/java/com/android/server/connectivity/Android.bp new file mode 100644 index 000000000000..a374ec2cea9a --- /dev/null +++ b/services/core/java/com/android/server/connectivity/Android.bp @@ -0,0 +1,10 @@ +aconfig_declarations { + name: "connectivity_flags", + package: "com.android.server.connectivity", + srcs: ["flags.aconfig"], +} + +java_aconfig_library { + name: "connectivity_flags_lib", + aconfig_declarations: "connectivity_flags", +} diff --git a/services/core/java/com/android/server/connectivity/flags.aconfig b/services/core/java/com/android/server/connectivity/flags.aconfig new file mode 100644 index 000000000000..32593d4bcdaa --- /dev/null +++ b/services/core/java/com/android/server/connectivity/flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.connectivity" + +flag { + name: "replace_vpn_profile_store" + namespace: "android_core_networking" + description: "This flag controls the usage of VpnBlobStore to replace LegacyVpnProfileStore." + bug: "307903113" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 77119d5ac384..f32c11d90c0d 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1197,54 +1197,11 @@ public class InputManagerService extends IInputManager.Stub } @Override // Binder call - public KeyboardLayout[] getKeyboardLayoutsForInputDevice( - final InputDeviceIdentifier identifier) { - return mKeyboardLayoutManager.getKeyboardLayoutsForInputDevice(identifier); - } - - @Override // Binder call public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { return mKeyboardLayoutManager.getKeyboardLayout(keyboardLayoutDescriptor); } @Override // Binder call - public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) { - return mKeyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(identifier); - } - - @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT) - @Override // Binder call - public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { - super.setCurrentKeyboardLayoutForInputDevice_enforcePermission(); - mKeyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(identifier, - keyboardLayoutDescriptor); - } - - @Override // Binder call - public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { - return mKeyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(identifier); - } - - @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT) - @Override // Binder call - public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { - super.addKeyboardLayoutForInputDevice_enforcePermission(); - mKeyboardLayoutManager.addKeyboardLayoutForInputDevice(identifier, - keyboardLayoutDescriptor); - } - - @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT) - @Override // Binder call - public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { - super.removeKeyboardLayoutForInputDevice_enforcePermission(); - mKeyboardLayoutManager.removeKeyboardLayoutForInputDevice(identifier, - keyboardLayoutDescriptor); - } - - @Override // Binder call public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { @@ -1270,11 +1227,6 @@ public class InputManagerService extends IInputManager.Stub imeInfo, imeSubtype); } - - public void switchKeyboardLayout(int deviceId, int direction) { - mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction); - } - public void setFocusedApplication(int displayId, InputApplicationHandle application) { mNative.setFocusedApplication(displayId, application); } diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 661008103a25..9ba647fb1d2d 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -60,7 +60,6 @@ import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -69,7 +68,6 @@ import android.view.KeyCharacterMap; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; -import android.widget.Toast; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -96,7 +94,6 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.stream.Stream; /** * A component of {@link InputManagerService} responsible for managing Physical Keyboard layouts. @@ -112,9 +109,8 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int MSG_UPDATE_EXISTING_DEVICES = 1; - private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2; - private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3; - private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4; + private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2; + private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3; private final Context mContext; private final NativeInputManagerService mNative; @@ -126,13 +122,14 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { // Connected keyboards with associated keyboard layouts (either auto-detected or manually // selected layout). private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>(); - private Toast mSwitchedKeyboardLayoutToast; // This cache stores "best-matched" layouts so that we don't need to run the matching // algorithm repeatedly. @GuardedBy("mKeyboardLayoutCache") private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache = new ArrayMap<>(); + + private HashSet<String> mAvailableLayouts = new HashSet<>(); private final Object mImeInfoLock = new Object(); @Nullable @GuardedBy("mImeInfoLock") @@ -206,68 +203,51 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } boolean needToShowNotification = false; - if (!useNewSettingsUi()) { - synchronized (mDataStore) { - String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier()); - if (layout == null) { - layout = getDefaultKeyboardLayout(inputDevice); - if (layout != null) { - setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout); - } - } - if (layout == null) { - // In old settings show notification always until user manually selects a - // layout in the settings. - needToShowNotification = true; - } - } - } else { - Set<String> selectedLayouts = new HashSet<>(); - List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping(); - List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>(); - boolean hasMissingLayout = false; - for (ImeInfo imeInfo : imeInfoList) { - // Check if the layout has been previously configured - KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( - keyboardIdentifier, imeInfo); - boolean noLayoutFound = result.getLayoutDescriptor() == null; - if (!noLayoutFound) { - selectedLayouts.add(result.getLayoutDescriptor()); - } - resultList.add(result); - hasMissingLayout |= noLayoutFound; + Set<String> selectedLayouts = new HashSet<>(); + List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping(); + List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>(); + boolean hasMissingLayout = false; + for (ImeInfo imeInfo : imeInfoList) { + // Check if the layout has been previously configured + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( + keyboardIdentifier, imeInfo); + if (result.getLayoutDescriptor() != null) { + selectedLayouts.add(result.getLayoutDescriptor()); + } else { + hasMissingLayout = true; } + resultList.add(result); + } - if (DEBUG) { - Slog.d(TAG, - "Layouts selected for input device: " + keyboardIdentifier - + " -> selectedLayouts: " + selectedLayouts); - } + if (DEBUG) { + Slog.d(TAG, + "Layouts selected for input device: " + keyboardIdentifier + + " -> selectedLayouts: " + selectedLayouts); + } - // If even one layout not configured properly, we need to ask user to configure - // the keyboard properly from the Settings. - if (hasMissingLayout) { - selectedLayouts.clear(); - } + // If even one layout not configured properly, we need to ask user to configure + // the keyboard properly from the Settings. + if (hasMissingLayout) { + selectedLayouts.clear(); + } - config.setConfiguredLayouts(selectedLayouts); + config.setConfiguredLayouts(selectedLayouts); - synchronized (mDataStore) { - try { - final String key = keyboardIdentifier.toString(); - if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) { - // Need to show the notification only if layout selection changed - // from the previous configuration - needToShowNotification = true; - } + synchronized (mDataStore) { + try { + final String key = keyboardIdentifier.toString(); + if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) { + // Need to show the notification only if layout selection changed + // from the previous configuration + needToShowNotification = true; + } - if (shouldLogConfiguration) { - logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList, - !mDataStore.hasInputDeviceEntry(key)); - } - } finally { - mDataStore.saveIfNeeded(); + if (shouldLogConfiguration) { + logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList, + !mDataStore.hasInputDeviceEntry(key)); } + } finally { + mDataStore.saveIfNeeded(); } } if (needToShowNotification) { @@ -275,63 +255,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } - private String getDefaultKeyboardLayout(final InputDevice inputDevice) { - final Locale systemLocale = mContext.getResources().getConfiguration().locale; - // If our locale doesn't have a language for some reason, then we don't really have a - // reasonable default. - if (TextUtils.isEmpty(systemLocale.getLanguage())) { - return null; - } - final List<KeyboardLayout> layouts = new ArrayList<>(); - visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> { - // Only select a default when we know the layout is appropriate. For now, this - // means it's a custom layout for a specific keyboard. - if (layout.getVendorId() != inputDevice.getVendorId() - || layout.getProductId() != inputDevice.getProductId()) { - return; - } - final LocaleList locales = layout.getLocales(); - for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) { - final Locale locale = locales.get(localeIndex); - if (locale != null && isCompatibleLocale(systemLocale, locale)) { - layouts.add(layout); - break; - } - } - }); - - if (layouts.isEmpty()) { - return null; - } - - // First sort so that ones with higher priority are listed at the top - Collections.sort(layouts); - // Next we want to try to find an exact match of language, country and variant. - for (KeyboardLayout layout : layouts) { - final LocaleList locales = layout.getLocales(); - for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) { - final Locale locale = locales.get(localeIndex); - if (locale != null && locale.getCountry().equals(systemLocale.getCountry()) - && locale.getVariant().equals(systemLocale.getVariant())) { - return layout.getDescriptor(); - } - } - } - // Then try an exact match of language and country - for (KeyboardLayout layout : layouts) { - final LocaleList locales = layout.getLocales(); - for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) { - final Locale locale = locales.get(localeIndex); - if (locale != null && locale.getCountry().equals(systemLocale.getCountry())) { - return layout.getDescriptor(); - } - } - } - - // Give up and just use the highest priority layout with matching language - return layouts.get(0).getDescriptor(); - } - private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) { // Different languages are never compatible if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) { @@ -343,11 +266,19 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { || systemLocale.getCountry().equals(keyboardLocale.getCountry()); } + @MainThread private void updateKeyboardLayouts() { // Scan all input devices state for keyboard layouts that have been uninstalled. - final HashSet<String> availableKeyboardLayouts = new HashSet<String>(); + final HashSet<String> availableKeyboardLayouts = new HashSet<>(); visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> availableKeyboardLayouts.add(layout.getDescriptor())); + + // If available layouts don't change, there is no need to reload layouts. + if (mAvailableLayouts.equals(availableKeyboardLayouts)) { + return; + } + mAvailableLayouts = availableKeyboardLayouts; + synchronized (mDataStore) { try { mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts); @@ -374,53 +305,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @AnyThread - public KeyboardLayout[] getKeyboardLayoutsForInputDevice( - final InputDeviceIdentifier identifier) { - if (useNewSettingsUi()) { - // Provide all supported keyboard layouts since Ime info is not provided - return getKeyboardLayouts(); - } - final String[] enabledLayoutDescriptors = - getEnabledKeyboardLayoutsForInputDevice(identifier); - final ArrayList<KeyboardLayout> enabledLayouts = - new ArrayList<>(enabledLayoutDescriptors.length); - final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>(); - visitAllKeyboardLayouts(new KeyboardLayoutVisitor() { - boolean mHasSeenDeviceSpecificLayout; - - @Override - public void visitKeyboardLayout(Resources resources, - int keyboardLayoutResId, KeyboardLayout layout) { - // First check if it's enabled. If the keyboard layout is enabled then we always - // want to return it as a possible layout for the device. - for (String s : enabledLayoutDescriptors) { - if (s != null && s.equals(layout.getDescriptor())) { - enabledLayouts.add(layout); - return; - } - } - // Next find any potential layouts that aren't yet enabled for the device. For - // devices that have special layouts we assume there's a reason that the generic - // layouts don't work for them so we don't want to return them since it's likely - // to result in a poor user experience. - if (layout.getVendorId() == identifier.getVendorId() - && layout.getProductId() == identifier.getProductId()) { - if (!mHasSeenDeviceSpecificLayout) { - mHasSeenDeviceSpecificLayout = true; - potentialLayouts.clear(); - } - potentialLayouts.add(layout); - } else if (layout.getVendorId() == -1 && layout.getProductId() == -1 - && !mHasSeenDeviceSpecificLayout) { - potentialLayouts.add(layout); - } - } - }); - return Stream.concat(enabledLayouts.stream(), potentialLayouts.stream()).toArray( - KeyboardLayout[]::new); - } - - @AnyThread @Nullable public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) { Objects.requireNonNull(keyboardLayoutDescriptor, @@ -580,195 +464,16 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { return LocaleList.forLanguageTags(languageTags.replace('|', ',')); } - @AnyThread - @Nullable - public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) { - if (useNewSettingsUi()) { - Slog.e(TAG, "getCurrentKeyboardLayoutForInputDevice API not supported"); - return null; - } - String key = new KeyboardIdentifier(identifier).toString(); - synchronized (mDataStore) { - String layout; - // try loading it using the layout descriptor if we have it - layout = mDataStore.getCurrentKeyboardLayout(key); - if (layout == null && !key.equals(identifier.getDescriptor())) { - // if it doesn't exist fall back to the device descriptor - layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor()); - } - if (DEBUG) { - Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() " - + identifier.toString() + ": " + layout); - } - return layout; - } - } - - @AnyThread - public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { - if (useNewSettingsUi()) { - Slog.e(TAG, "setCurrentKeyboardLayoutForInputDevice API not supported"); - return; - } - - Objects.requireNonNull(keyboardLayoutDescriptor, - "keyboardLayoutDescriptor must not be null"); - String key = new KeyboardIdentifier(identifier).toString(); - synchronized (mDataStore) { - try { - if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) { - if (DEBUG) { - Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier - + " key: " + key - + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor); - } - mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); - } - } finally { - mDataStore.saveIfNeeded(); - } - } - } - - @AnyThread - public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { - if (useNewSettingsUi()) { - Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported"); - return new String[0]; - } - String key = new KeyboardIdentifier(identifier).toString(); - synchronized (mDataStore) { - String[] layouts = mDataStore.getKeyboardLayouts(key); - if ((layouts == null || layouts.length == 0) - && !key.equals(identifier.getDescriptor())) { - layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor()); - } - return layouts; - } - } - - @AnyThread - public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { - if (useNewSettingsUi()) { - Slog.e(TAG, "addKeyboardLayoutForInputDevice API not supported"); - return; - } - Objects.requireNonNull(keyboardLayoutDescriptor, - "keyboardLayoutDescriptor must not be null"); - - String key = new KeyboardIdentifier(identifier).toString(); - synchronized (mDataStore) { - try { - String oldLayout = mDataStore.getCurrentKeyboardLayout(key); - if (oldLayout == null && !key.equals(identifier.getDescriptor())) { - oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor()); - } - if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor) - && !Objects.equals(oldLayout, - mDataStore.getCurrentKeyboardLayout(key))) { - mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); - } - } finally { - mDataStore.saveIfNeeded(); - } - } - } - - @AnyThread - public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { - if (useNewSettingsUi()) { - Slog.e(TAG, "removeKeyboardLayoutForInputDevice API not supported"); - return; - } - Objects.requireNonNull(keyboardLayoutDescriptor, - "keyboardLayoutDescriptor must not be null"); - - String key = new KeyboardIdentifier(identifier).toString(); - synchronized (mDataStore) { - try { - String oldLayout = mDataStore.getCurrentKeyboardLayout(key); - if (oldLayout == null && !key.equals(identifier.getDescriptor())) { - oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor()); - } - boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor); - if (!key.equals(identifier.getDescriptor())) { - // We need to remove from both places to ensure it is gone - removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(), - keyboardLayoutDescriptor); - } - if (removed && !Objects.equals(oldLayout, - mDataStore.getCurrentKeyboardLayout(key))) { - mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); - } - } finally { - mDataStore.saveIfNeeded(); - } - } - } - - @AnyThread - public void switchKeyboardLayout(int deviceId, int direction) { - if (useNewSettingsUi()) { - Slog.e(TAG, "switchKeyboardLayout API not supported"); - return; - } - mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget(); - } - - @MainThread - private void handleSwitchKeyboardLayout(int deviceId, int direction) { - final InputDevice device = getInputDevice(deviceId); - if (device != null) { - final boolean changed; - final String keyboardLayoutDescriptor; - - String key = new KeyboardIdentifier(device.getIdentifier()).toString(); - synchronized (mDataStore) { - try { - changed = mDataStore.switchKeyboardLayout(key, direction); - keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout( - key); - } finally { - mDataStore.saveIfNeeded(); - } - } - - if (changed) { - if (mSwitchedKeyboardLayoutToast != null) { - mSwitchedKeyboardLayoutToast.cancel(); - mSwitchedKeyboardLayoutToast = null; - } - if (keyboardLayoutDescriptor != null) { - KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor); - if (keyboardLayout != null) { - mSwitchedKeyboardLayoutToast = Toast.makeText( - mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT); - mSwitchedKeyboardLayoutToast.show(); - } - } - - reloadKeyboardLayouts(); - } - } - } - @Nullable @AnyThread public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier, String languageTag, String layoutType) { String keyboardLayoutDescriptor; - if (useNewSettingsUi()) { - synchronized (mImeInfoLock) { - KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( - new KeyboardIdentifier(identifier, languageTag, layoutType), - mCurrentImeInfo); - keyboardLayoutDescriptor = result.getLayoutDescriptor(); - } - } else { - keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier); + synchronized (mImeInfoLock) { + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( + new KeyboardIdentifier(identifier, languageTag, layoutType), + mCurrentImeInfo); + keyboardLayoutDescriptor = result.getLayoutDescriptor(); } if (keyboardLayoutDescriptor == null) { return null; @@ -797,10 +502,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { - if (!useNewSettingsUi()) { - Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported"); - return FAILED; - } InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return FAILED; @@ -820,10 +521,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) { - if (!useNewSettingsUi()) { - Slog.e(TAG, "setKeyboardLayoutForInputDevice() API not supported"); - return; - } Objects.requireNonNull(keyboardLayoutDescriptor, "keyboardLayoutDescriptor must not be null"); InputDevice inputDevice = getInputDevice(identifier); @@ -854,10 +551,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { - if (!useNewSettingsUi()) { - Slog.e(TAG, "getKeyboardLayoutListForInputDevice() API not supported"); - return new KeyboardLayout[0]; - } InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return new KeyboardLayout[0]; @@ -923,10 +616,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { public void onInputMethodSubtypeChanged(@UserIdInt int userId, @Nullable InputMethodSubtypeHandle subtypeHandle, @Nullable InputMethodSubtype subtype) { - if (!useNewSettingsUi()) { - Slog.e(TAG, "onInputMethodSubtypeChanged() API not supported"); - return; - } if (subtypeHandle == null) { if (DEBUG) { Slog.d(TAG, "No InputMethod is running, ignoring change"); @@ -1289,9 +978,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { onInputDeviceAdded(deviceId); } return true; - case MSG_SWITCH_KEYBOARD_LAYOUT: - handleSwitchKeyboardLayout(msg.arg1, msg.arg2); - return true; case MSG_RELOAD_KEYBOARD_LAYOUTS: reloadKeyboardLayouts(); return true; @@ -1303,10 +989,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } - private boolean useNewSettingsUi() { - return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI); - } - @Nullable private InputDevice getInputDevice(int deviceId) { InputManager inputManager = mContext.getSystemService(InputManager.class); diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java index 31083fd5de03..7859253d66e0 100644 --- a/services/core/java/com/android/server/input/PersistentDataStore.java +++ b/services/core/java/com/android/server/input/PersistentDataStore.java @@ -27,7 +27,6 @@ import android.util.Xml; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -42,7 +41,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -74,7 +72,7 @@ final class PersistentDataStore { new HashMap<String, InputDeviceState>(); // The interface for methods which should be replaced by the test harness. - private Injector mInjector; + private final Injector mInjector; // True if the data has been loaded. private boolean mLoaded; @@ -83,7 +81,7 @@ final class PersistentDataStore { private boolean mDirty; // Storing key remapping - private Map<Integer, Integer> mKeyRemapping = new HashMap<>(); + private final Map<Integer, Integer> mKeyRemapping = new HashMap<>(); public PersistentDataStore() { this(new Injector()); @@ -130,22 +128,6 @@ final class PersistentDataStore { } @Nullable - public String getCurrentKeyboardLayout(String inputDeviceDescriptor) { - InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); - return state != null ? state.getCurrentKeyboardLayout() : null; - } - - public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor, - String keyboardLayoutDescriptor) { - InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); - if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) { - setDirty(); - return true; - } - return false; - } - - @Nullable public String getKeyboardLayout(String inputDeviceDescriptor, String key) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); return state != null ? state.getKeyboardLayout(key) : null; @@ -171,43 +153,6 @@ final class PersistentDataStore { return false; } - public String[] getKeyboardLayouts(String inputDeviceDescriptor) { - InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); - if (state == null) { - return (String[])ArrayUtils.emptyArray(String.class); - } - return state.getKeyboardLayouts(); - } - - public boolean addKeyboardLayout(String inputDeviceDescriptor, - String keyboardLayoutDescriptor) { - InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); - if (state.addKeyboardLayout(keyboardLayoutDescriptor)) { - setDirty(); - return true; - } - return false; - } - - public boolean removeKeyboardLayout(String inputDeviceDescriptor, - String keyboardLayoutDescriptor) { - InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); - if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) { - setDirty(); - return true; - } - return false; - } - - public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) { - InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); - if (state != null && state.switchKeyboardLayout(direction)) { - setDirty(); - return true; - } - return false; - } - public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId, int brightness) { InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); @@ -417,9 +362,6 @@ final class PersistentDataStore { "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" }; private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4]; - @Nullable - private String mCurrentKeyboardLayout; - private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>(); private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray(); private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>(); @@ -465,49 +407,6 @@ final class PersistentDataStore { return true; } - @Nullable - public String getCurrentKeyboardLayout() { - return mCurrentKeyboardLayout; - } - - public boolean setCurrentKeyboardLayout(String keyboardLayout) { - if (Objects.equals(mCurrentKeyboardLayout, keyboardLayout)) { - return false; - } - addKeyboardLayout(keyboardLayout); - mCurrentKeyboardLayout = keyboardLayout; - return true; - } - - public String[] getKeyboardLayouts() { - if (mKeyboardLayouts.isEmpty()) { - return (String[])ArrayUtils.emptyArray(String.class); - } - return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]); - } - - public boolean addKeyboardLayout(String keyboardLayout) { - int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout); - if (index >= 0) { - return false; - } - mKeyboardLayouts.add(-index - 1, keyboardLayout); - if (mCurrentKeyboardLayout == null) { - mCurrentKeyboardLayout = keyboardLayout; - } - return true; - } - - public boolean removeKeyboardLayout(String keyboardLayout) { - int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout); - if (index < 0) { - return false; - } - mKeyboardLayouts.remove(index); - updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index); - return true; - } - public boolean setKeyboardBacklightBrightness(int lightId, int brightness) { if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) { return false; @@ -521,48 +420,8 @@ final class PersistentDataStore { return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness); } - private void updateCurrentKeyboardLayoutIfRemoved( - String removedKeyboardLayout, int removedIndex) { - if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) { - if (!mKeyboardLayouts.isEmpty()) { - int index = removedIndex; - if (index == mKeyboardLayouts.size()) { - index = 0; - } - mCurrentKeyboardLayout = mKeyboardLayouts.get(index); - } else { - mCurrentKeyboardLayout = null; - } - } - } - - public boolean switchKeyboardLayout(int direction) { - final int size = mKeyboardLayouts.size(); - if (size < 2) { - return false; - } - int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout); - assert index >= 0; - if (direction > 0) { - index = (index + 1) % size; - } else { - index = (index + size - 1) % size; - } - mCurrentKeyboardLayout = mKeyboardLayouts.get(index); - return true; - } - public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) { boolean changed = false; - for (int i = mKeyboardLayouts.size(); i-- > 0; ) { - String keyboardLayout = mKeyboardLayouts.get(i); - if (!availableKeyboardLayouts.contains(keyboardLayout)) { - Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout); - mKeyboardLayouts.remove(i); - updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i); - changed = true; - } - } List<String> removedEntries = new ArrayList<>(); for (String key : mKeyboardLayoutMap.keySet()) { if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) { @@ -582,27 +441,7 @@ final class PersistentDataStore { throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { - if (parser.getName().equals("keyboard-layout")) { - String descriptor = parser.getAttributeValue(null, "descriptor"); - if (descriptor == null) { - throw new XmlPullParserException( - "Missing descriptor attribute on keyboard-layout."); - } - String current = parser.getAttributeValue(null, "current"); - if (mKeyboardLayouts.contains(descriptor)) { - throw new XmlPullParserException( - "Found duplicate keyboard layout."); - } - - mKeyboardLayouts.add(descriptor); - if (current != null && current.equals("true")) { - if (mCurrentKeyboardLayout != null) { - throw new XmlPullParserException( - "Found multiple current keyboard layouts."); - } - mCurrentKeyboardLayout = descriptor; - } - } else if (parser.getName().equals("keyed-keyboard-layout")) { + if (parser.getName().equals("keyed-keyboard-layout")) { String key = parser.getAttributeValue(null, "key"); if (key == null) { throw new XmlPullParserException( @@ -676,27 +515,9 @@ final class PersistentDataStore { } } } - - // Maintain invariant that layouts are sorted. - Collections.sort(mKeyboardLayouts); - - // Maintain invariant that there is always a current keyboard layout unless - // there are none installed. - if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) { - mCurrentKeyboardLayout = mKeyboardLayouts.get(0); - } } public void saveToXml(TypedXmlSerializer serializer) throws IOException { - for (String layout : mKeyboardLayouts) { - serializer.startTag(null, "keyboard-layout"); - serializer.attribute(null, "descriptor", layout); - if (layout.equals(mCurrentKeyboardLayout)) { - serializer.attributeBoolean(null, "current", true); - } - serializer.endTag(null, "keyboard-layout"); - } - for (String key : mKeyboardLayoutMap.keySet()) { serializer.startTag(null, "keyed-keyboard-layout"); serializer.attribute(null, "key", key); diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 0cd7654f70ea..dfb2b0a750e3 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -157,6 +157,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl { } @Override + public void expireTempEngaged() { + // NA as MediaSession2 doesn't support UserEngagementStates for FGS. + } + + @Override public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb) { // TODO(jaewan): Implement. diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index eb4e6e44c8a4..8f164d361a56 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -24,6 +24,7 @@ import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_L import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -85,6 +86,8 @@ import com.android.server.LocalServices; import com.android.server.uri.UriGrantsManagerInternal; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -225,6 +228,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde private int mPolicies; + private @UserEngagementState int mUserEngagementState = USER_DISENGAGED; + + @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED}) + @Retention(RetentionPolicy.SOURCE) + private @interface UserEngagementState {} + + /** + * Indicates that the session is active and in one of the user engaged states. + * + * @see #updateUserEngagedStateIfNeededLocked(boolean) () + */ + private static final int USER_PERMANENTLY_ENGAGED = 0; + + /** + * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state. + * + * @see #updateUserEngagedStateIfNeededLocked(boolean) () + */ + private static final int USER_TEMPORARY_ENGAGED = 1; + + /** + * Indicates that the session is either not active or in one of the user disengaged states + * + * @see #updateUserEngagedStateIfNeededLocked(boolean) () + */ + private static final int USER_DISENGAGED = 2; + + /** + * Indicates the duration of the temporary engaged states. + * + * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily + * engaged, meaning the corresponding session is only considered in an engaged state for the + * duration of this timeout, and only if coming from an engaged state. + * + * <p>For example, if a session is transitioning from a user-engaged state {@link + * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link + * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for + * the duration of this timeout, starting at the transition instant. However, a temporary + * user-engaged state is not considered user-engaged when transitioning from a non-user engaged + * state {@link PlaybackState#STATE_STOPPED}. + */ + private static final int TEMP_USER_ENGAGED_TIMEOUT = 600000; + public MediaSessionRecord( int ownerPid, int ownerUid, @@ -548,6 +594,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde mSessionCb.mCb.asBinder().unlinkToDeath(this, 0); mDestroyed = true; mPlaybackState = null; + updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); mHandler.post(MessageHandler.MSG_DESTROYED); } } @@ -559,6 +606,12 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } + @Override + public void expireTempEngaged() { + mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout); + updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + } + /** * Sends media button. * @@ -849,7 +902,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } if (deadCallbackHolders != null) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); + removeControllerHoldersSafely(deadCallbackHolders); } } @@ -876,7 +929,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } if (deadCallbackHolders != null) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); + removeControllerHoldersSafely(deadCallbackHolders); } } @@ -911,7 +964,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } if (deadCallbackHolders != null) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); + removeControllerHoldersSafely(deadCallbackHolders); } } @@ -938,7 +991,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } if (deadCallbackHolders != null) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); + removeControllerHoldersSafely(deadCallbackHolders); } } @@ -965,7 +1018,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } if (deadCallbackHolders != null) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); + removeControllerHoldersSafely(deadCallbackHolders); } } @@ -992,7 +1045,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } if (deadCallbackHolders != null) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); + removeControllerHoldersSafely(deadCallbackHolders); } } @@ -1017,7 +1070,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } if (deadCallbackHolders != null) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); + removeControllerHoldersSafely(deadCallbackHolders); } } @@ -1042,7 +1095,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } } // After notifying clear all listeners - mControllerCallbackHolders.clear(); + removeControllerHoldersSafely(null); } private PlaybackState getStateWithUpdatedPosition() { @@ -1090,6 +1143,17 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return -1; } + private void removeControllerHoldersSafely( + Collection<ISessionControllerCallbackHolder> holders) { + synchronized (mLock) { + if (holders == null) { + mControllerCallbackHolders.clear(); + } else { + mControllerCallbackHolders.removeAll(holders); + } + } + } + private PlaybackInfo getVolumeAttributes() { int volumeType; AudioAttributes attributes; @@ -1118,6 +1182,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } }; + private final Runnable mHandleTempEngagedSessionTimeout = + () -> { + updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + }; + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) private static boolean componentNameExists( @NonNull ComponentName componentName, @NonNull Context context, int userId) { @@ -1134,6 +1203,40 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return !resolveInfos.isEmpty(); } + private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) { + int oldUserEngagedState = mUserEngagementState; + int newUserEngagedState; + if (!isActive() || mPlaybackState == null) { + newUserEngagedState = USER_DISENGAGED; + } else if (isActive() && mPlaybackState.isActive()) { + newUserEngagedState = USER_PERMANENTLY_ENGAGED; + } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) { + newUserEngagedState = + oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired + ? USER_TEMPORARY_ENGAGED + : USER_DISENGAGED; + } else { + newUserEngagedState = USER_DISENGAGED; + } + if (oldUserEngagedState == newUserEngagedState) { + return; + } + + if (newUserEngagedState == USER_TEMPORARY_ENGAGED) { + mHandler.postDelayed(mHandleTempEngagedSessionTimeout, TEMP_USER_ENGAGED_TIMEOUT); + } else if (oldUserEngagedState == USER_TEMPORARY_ENGAGED) { + mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout); + } + + boolean wasUserEngaged = oldUserEngagedState != USER_DISENGAGED; + boolean isNowUserEngaged = newUserEngagedState != USER_DISENGAGED; + mUserEngagementState = newUserEngagedState; + if (wasUserEngaged != isNowUserEngaged) { + mService.onSessionUserEngagementStateChange( + /* mediaSessionRecord= */ this, /* isUserEngaged= */ isNowUserEngaged); + } + } + private final class SessionStub extends ISession.Stub { @Override public void destroySession() throws RemoteException { @@ -1171,8 +1274,10 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK, callingUid, callingPid); } - - mIsActive = active; + synchronized (mLock) { + mIsActive = active; + updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); + } long token = Binder.clearCallingIdentity(); try { mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState); @@ -1330,6 +1435,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde && TRANSITION_PRIORITY_STATES.contains(newState)); synchronized (mLock) { mPlaybackState = state; + updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); } final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 09991995099e..b57b14835987 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -196,6 +196,12 @@ public abstract class MediaSessionRecordImpl { */ public abstract boolean isClosed(); + /** + * Note: This method is only used for testing purposes If the session is temporary engaged, the + * timeout will expire and it will become disengaged. + */ + public abstract void expireTempEngaged(); + @Override public final boolean equals(Object o) { if (this == o) return true; diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 53c32cf31d21..2ca76a578f53 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -367,11 +367,13 @@ public class MediaSessionService extends SystemService implements Monitor { } boolean isUserEngaged = isUserEngaged(record, playbackState); - Log.d(TAG, "onSessionActiveStateChanged: " - + "record=" + record - + "playbackState=" + playbackState - + "allowRunningInForeground=" + isUserEngaged); - setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged); + Log.d( + TAG, + "onSessionActiveStateChanged:" + + " record=" + + record + + " playbackState=" + + playbackState); reportMediaInteractionEvent(record, isUserEngaged); mHandler.postSessionsChanged(record); } @@ -479,11 +481,13 @@ public class MediaSessionService extends SystemService implements Monitor { } user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority); boolean isUserEngaged = isUserEngaged(record, playbackState); - Log.d(TAG, "onSessionPlaybackStateChanged: " - + "record=" + record - + "playbackState=" + playbackState - + "allowRunningInForeground=" + isUserEngaged); - setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged); + Log.d( + TAG, + "onSessionPlaybackStateChanged:" + + " record=" + + record + + " playbackState=" + + playbackState); reportMediaInteractionEvent(record, isUserEngaged); } } @@ -650,33 +654,54 @@ public class MediaSessionService extends SystemService implements Monitor { session.close(); Log.d(TAG, "destroySessionLocked: record=" + session); - setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false); + reportMediaInteractionEvent(session, /* userEngaged= */ false); mHandler.postSessionsChanged(session); } - private void setForegroundServiceAllowance( - MediaSessionRecordImpl record, boolean allowRunningInForeground) { - if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { - return; - } - ForegroundServiceDelegationOptions foregroundServiceDelegationOptions = - record.getForegroundServiceDelegationOptions(); - if (foregroundServiceDelegationOptions == null) { - return; - } - if (allowRunningInForeground) { - onUserSessionEngaged(record); + void onSessionUserEngagementStateChange( + MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) { + if (isUserEngaged) { + addUserEngagedSession(mediaSessionRecord); + startFgsIfSessionIsLinkedToNotification(mediaSessionRecord); } else { - onUserDisengaged(record); + removeUserEngagedSession(mediaSessionRecord); + stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord); } } - private void onUserSessionEngaged(MediaSessionRecordImpl mediaSessionRecord) { + private void addUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) { synchronized (mLock) { int uid = mediaSessionRecord.getUid(); mUserEngagedSessionsForFgs.putIfAbsent(uid, new HashSet<>()); mUserEngagedSessionsForFgs.get(uid).add(mediaSessionRecord); + } + } + + private void removeUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) { + synchronized (mLock) { + int uid = mediaSessionRecord.getUid(); + Set<MediaSessionRecordImpl> mUidUserEngagedSessionsForFgs = + mUserEngagedSessionsForFgs.get(uid); + if (mUidUserEngagedSessionsForFgs == null) { + return; + } + + mUidUserEngagedSessionsForFgs.remove(mediaSessionRecord); + if (mUidUserEngagedSessionsForFgs.isEmpty()) { + mUserEngagedSessionsForFgs.remove(uid); + } + } + } + + private void startFgsIfSessionIsLinkedToNotification( + MediaSessionRecordImpl mediaSessionRecord) { + Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord); + if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { + return; + } + synchronized (mLock) { + int uid = mediaSessionRecord.getUid(); for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) { if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) { mActivityManagerInternal.startForegroundServiceDelegate( @@ -688,30 +713,34 @@ public class MediaSessionService extends SystemService implements Monitor { } } - private void onUserDisengaged(MediaSessionRecordImpl mediaSessionRecord) { + private void stopFgsIfNoSessionIsLinkedToNotification( + MediaSessionRecordImpl mediaSessionRecord) { + Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord); + if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { + return; + } synchronized (mLock) { int uid = mediaSessionRecord.getUid(); - if (mUserEngagedSessionsForFgs.containsKey(uid)) { - mUserEngagedSessionsForFgs.get(uid).remove(mediaSessionRecord); - if (mUserEngagedSessionsForFgs.get(uid).isEmpty()) { - mUserEngagedSessionsForFgs.remove(uid); - } + ForegroundServiceDelegationOptions foregroundServiceDelegationOptions = + mediaSessionRecord.getForegroundServiceDelegationOptions(); + if (foregroundServiceDelegationOptions == null) { + return; } - boolean shouldStopFgs = true; - for (MediaSessionRecordImpl sessionRecord : + for (MediaSessionRecordImpl record : mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) { - for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, - Set.of())) { - if (sessionRecord.isLinkedToNotification(mediaNotification)) { - shouldStopFgs = false; + for (Notification mediaNotification : + mMediaNotifications.getOrDefault(uid, Set.of())) { + if (record.isLinkedToNotification(mediaNotification)) { + // A user engaged session linked with a media notification is found. + // We shouldn't call stop FGS in this case. + return; } } } - if (shouldStopFgs) { - mActivityManagerInternal.stopForegroundServiceDelegate( - mediaSessionRecord.getForegroundServiceDelegationOptions()); - } + + mActivityManagerInternal.stopForegroundServiceDelegate( + foregroundServiceDelegationOptions); } } @@ -2502,7 +2531,6 @@ public class MediaSessionService extends SystemService implements Monitor { } MediaSessionRecord session = null; MediaButtonReceiverHolder mediaButtonReceiverHolder = null; - if (mCustomMediaKeyDispatcher != null) { MediaSession.Token token = mCustomMediaKeyDispatcher.getMediaSession( keyEvent, uid, asSystemService); @@ -2630,6 +2658,18 @@ public class MediaSessionService extends SystemService implements Monitor { && streamType <= AudioManager.STREAM_NOTIFICATION; } + @Override + public void expireTempEngagedSessions() { + synchronized (mLock) { + for (Set<MediaSessionRecordImpl> uidSessions : + mUserEngagedSessionsForFgs.values()) { + for (MediaSessionRecordImpl sessionRecord : uidSessions) { + sessionRecord.expireTempEngaged(); + } + } + } + } + private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable { private final String mPackageName; private final int mPid; @@ -3127,7 +3167,6 @@ public class MediaSessionService extends SystemService implements Monitor { super.onNotificationPosted(sbn); Notification postedNotification = sbn.getNotification(); int uid = sbn.getUid(); - if (!postedNotification.isMediaNotification()) { return; } @@ -3138,8 +3177,8 @@ public class MediaSessionService extends SystemService implements Monitor { mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) { ForegroundServiceDelegationOptions foregroundServiceDelegationOptions = mediaSessionRecord.getForegroundServiceDelegationOptions(); - if (mediaSessionRecord.isLinkedToNotification(postedNotification) - && foregroundServiceDelegationOptions != null) { + if (foregroundServiceDelegationOptions != null + && mediaSessionRecord.isLinkedToNotification(postedNotification)) { mActivityManagerInternal.startForegroundServiceDelegate( foregroundServiceDelegationOptions, /* connection= */ null); @@ -3173,21 +3212,7 @@ public class MediaSessionService extends SystemService implements Monitor { return; } - boolean shouldStopFgs = true; - for (MediaSessionRecordImpl mediaSessionRecord : - mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) { - for (Notification mediaNotification : - mMediaNotifications.getOrDefault(uid, Set.of())) { - if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) { - shouldStopFgs = false; - } - } - } - if (shouldStopFgs - && notificationRecord.getForegroundServiceDelegationOptions() != null) { - mActivityManagerInternal.stopForegroundServiceDelegate( - notificationRecord.getForegroundServiceDelegationOptions()); - } + stopFgsIfNoSessionIsLinkedToNotification(notificationRecord); } } diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java index a56380827f2c..a20de3198d2c 100644 --- a/services/core/java/com/android/server/media/MediaShellCommand.java +++ b/services/core/java/com/android/server/media/MediaShellCommand.java @@ -92,6 +92,8 @@ public class MediaShellCommand extends ShellCommand { runMonitor(); } else if (cmd.equals("volume")) { runVolume(); + } else if (cmd.equals("expire-temp-engaged-sessions")) { + expireTempEngagedSessions(); } else { showError("Error: unknown command '" + cmd + "'"); return -1; @@ -367,4 +369,8 @@ public class MediaShellCommand extends ShellCommand { private void runVolume() throws Exception { VolumeCtrl.run(this); } + + private void expireTempEngagedSessions() throws Exception { + mSessionService.expireTempEngagedSessions(); + } } diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 4eb8b2b980cb..c8fd7e47d80a 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -184,6 +184,16 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { throwInvalidBugreportFileForCallerException( bugreportFile, callingInfo.second); } + + boolean keepBugreportOnRetrieval = false; + if (onboardingBugreportV2Enabled()) { + keepBugreportOnRetrieval = mBugreportFilesToPersist.contains( + bugreportFile); + } + + if (!keepBugreportOnRetrieval) { + bugreportFilesForUid.remove(bugreportFile); + } } else { ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo); if (bugreportFilesForCaller != null diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 3eeeae7dc260..80a5f3a4c579 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1085,11 +1085,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isUpdateOwnershipEnforcementEnabled = mPm.isUpdateOwnershipEnforcementAvailable() && existingUpdateOwnerPackageName != null; + // For an installation that un-archives an app, if the installer doesn't have the + // INSTALL_PACKAGES permission, the user should have already been prompted to confirm the + // un-archive request. There's no need for another confirmation during the installation. + final boolean isInstallUnarchive = + (params.installFlags & PackageManager.INSTALL_UNARCHIVE) != 0; // Device owners and affiliated profile owners are allowed to silently install packages, so // the permission check is waived if the installer is the device owner. final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem - || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall; + || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall + || isInstallUnarchive; if (noUserActionNecessary) { return userActionNotTypicallyNeededResponse; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7db83d7dcc6f..b4919a4fb9ff 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -174,7 +174,6 @@ import android.service.dreams.IDreamManager; import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; -import android.util.FeatureFlagUtils; import android.util.Log; import android.util.MathUtils; import android.util.MutableBoolean; @@ -4121,14 +4120,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction, IBinder focusedToken) { - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { - IBinder targetWindowToken = - mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken); - InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction, - event.getDisplayId(), targetWindowToken); - } else { - mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); - } + IBinder targetWindowToken = + mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken); + InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction, + event.getDisplayId(), targetWindowToken); } private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent, diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 2623025cacf1..9ca4e273ac39 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -251,12 +251,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { */ public int getCameraLensCoverState(); - /** - * Switch the keyboard layout for the given device. - * Direction should be +1 or -1 to go to the next or previous keyboard layout. - */ - public void switchKeyboardLayout(int deviceId, int direction); - public void shutdown(boolean confirm); public void reboot(boolean confirm); public void rebootSafeMode(boolean confirm); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 8c4c0de71781..802f196adf3b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -42,6 +42,7 @@ import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG; import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir; import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked; import static com.android.window.flags.Flags.multiCrop; +import static com.android.window.flags.Flags.offloadColorExtraction; import android.annotation.NonNull; import android.app.ActivityManager; @@ -380,7 +381,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Outside of the lock since it will synchronize itself - notifyWallpaperColorsChanged(wallpaper); + if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wallpaper); } @Override @@ -403,12 +404,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper) { + notifyWallpaperColorsChanged(wallpaper, wallpaper.mWhich); + } + + private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) { if (DEBUG) { Slog.i(TAG, "Notifying wallpaper colors changed"); } if (wallpaper.connection != null) { wallpaper.connection.forEachDisplayConnector(connector -> - notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId)); + notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId, which)); } } @@ -425,6 +430,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper, int displayId) { + notifyWallpaperColorsChangedOnDisplay(wallpaper, displayId, wallpaper.mWhich); + } + + private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper, + int displayId, int which) { boolean needsExtraction; synchronized (mLock) { final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners = @@ -449,8 +459,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub notify = extractColors(wallpaper); } if (notify) { - notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), - wallpaper.mWhich, wallpaper.userId, displayId); + notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which, + wallpaper.userId, displayId); } } @@ -504,6 +514,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * @return true unless the wallpaper changed during the color computation */ private boolean extractColors(WallpaperData wallpaper) { + if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent); String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; @@ -1148,10 +1159,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Do not broadcast changes on ImageWallpaper since it's handled // internally by this class. - if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) { + boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent); + if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) { return; } mWallpaper.primaryColors = primaryColors; + // only save the colors for ImageWallpaper - for live wallpapers, the colors + // are always recomputed after a reboot. + if (offloadColorExtraction() && isImageWallpaper) { + saveSettingsLocked(mWallpaper.userId); + } } notifyWallpaperColorsChangedOnDisplay(mWallpaper, displayId); } @@ -1177,7 +1194,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { // This will trigger onComputeColors in the wallpaper engine. // It's fine to be locked in here since the binder is oneway. - connector.mEngine.requestWallpaperColors(); + if (!offloadColorExtraction() || mWallpaper.primaryColors == null) { + connector.mEngine.requestWallpaperColors(); + } } catch (RemoteException e) { Slog.w(TAG, "Failed to request wallpaper colors", e); } @@ -1811,6 +1830,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // Offload color extraction to another thread since switchUser will be called // from the main thread. FgThread.getHandler().post(() -> { + if (offloadColorExtraction()) return; notifyWallpaperColorsChanged(systemWallpaper); if (lockWallpaper != systemWallpaper) notifyWallpaperColorsChanged(lockWallpaper); notifyWallpaperColorsChanged(mFallbackWallpaper); @@ -2722,8 +2742,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub }); // Need to extract colors again to re-calculate dark hints after // applying dimming. - wp.mIsColorExtractedFromDim = true; - pendingColorExtraction.add(wp); + if (!offloadColorExtraction()) { + wp.mIsColorExtractedFromDim = true; + pendingColorExtraction.add(wp); + } changed = true; } } @@ -2732,7 +2754,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } for (WallpaperData wp: pendingColorExtraction) { - notifyWallpaperColorsChanged(wp); + if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wp); } } finally { Binder.restoreCallingIdentity(ident); @@ -2927,6 +2949,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } wallpaper.allowBackup = allowBackup; wallpaper.mWallpaperDimAmount = getWallpaperDimAmount(); + if (offloadColorExtraction()) wallpaper.primaryColors = null; } return pfd; } finally { @@ -3069,6 +3092,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); boolean shouldNotifyColors = false; + + // If the lockscreen wallpaper is set to the same as the home screen, notify that the + // lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine. + boolean shouldNotifyLockscreenColors = false; boolean bindSuccess; final WallpaperData newWallpaper; @@ -3114,7 +3141,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub bindSuccess = bindWallpaperComponentLocked(name, /* force */ forceRebind, /* fromUser */ true, newWallpaper, reply); if (bindSuccess) { - if (!same) { + if (!same || (offloadColorExtraction() && forceRebind)) { newWallpaper.primaryColors = null; } else { if (newWallpaper.connection != null) { @@ -3138,6 +3165,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub newWallpaper.wallpaperId = makeWallpaperIdLocked(); notifyCallbacksLocked(newWallpaper); shouldNotifyColors = true; + if (offloadColorExtraction()) { + shouldNotifyColors = false; + shouldNotifyLockscreenColors = !force && same && !systemIsBoth + && which == (FLAG_SYSTEM | FLAG_LOCK); + } if (which == (FLAG_SYSTEM | FLAG_LOCK)) { if (DEBUG) { @@ -3166,6 +3198,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (shouldNotifyColors) { notifyWallpaperColorsChanged(newWallpaper); } + if (shouldNotifyLockscreenColors) { + notifyWallpaperColorsChanged(newWallpaper, FLAG_LOCK); + } + return bindSuccess; } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index f6681c571090..f7b4a6748411 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -779,6 +780,10 @@ class BackNavigationController { && wc.asTaskFragment() == null) { continue; } + // Only care if visibility changed. + if (targets.get(i).getTransitMode(wc) == TRANSIT_CHANGE) { + continue; + } // WC can be visible due to setLaunchBehind if (wc.isVisibleRequested()) { mTmpOpenApps.add(wc); @@ -843,14 +848,14 @@ class BackNavigationController { * @param targets The final animation targets derived in transition. * @param finishedTransition The finished transition target. */ - boolean onTransitionFinish(ArrayList<Transition.ChangeInfo> targets, + void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets, @NonNull Transition finishedTransition) { if (finishedTransition == mWaitTransitionFinish) { clearBackAnimations(); } if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) { - return false; + return; } ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Handling the deferred animation after transition finished"); @@ -878,7 +883,7 @@ class BackNavigationController { + " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets) + " close: " + mPendingAnimationBuilder.mCloseTarget); cancelPendingAnimation(); - return false; + return; } // Ensure the final animation targets which hidden by transition could be visible. @@ -887,9 +892,14 @@ class BackNavigationController { wc.prepareSurfaces(); } - scheduleAnimation(mPendingAnimationBuilder); - mPendingAnimationBuilder = null; - return true; + // The pending builder could be cleared due to prepareSurfaces + // => updateNonSystemOverlayWindowsVisibilityIfNeeded + // => setForceHideNonSystemOverlayWindowIfNeeded + // => updateFocusedWindowLocked => onFocusWindowChanged. + if (mPendingAnimationBuilder != null) { + scheduleAnimation(mPendingAnimationBuilder); + mPendingAnimationBuilder = null; + } } private void cancelPendingAnimation() { @@ -1552,15 +1562,17 @@ class BackNavigationController { return this; } + // WC must be Activity/TaskFragment/Task boolean containTarget(@NonNull WindowContainer wc) { if (mOpenTargets != null) { for (int i = mOpenTargets.length - 1; i >= 0; --i) { - if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)) { + if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc) + || wc.hasChild(mOpenTargets[i])) { return true; } } } - return wc == mCloseTarget || mCloseTarget.hasChild(wc); + return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget); } /** diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index b8bb258aa2ce..0ad601de95ec 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -61,6 +61,7 @@ import android.view.WindowManager; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; +import com.android.window.flags.Flags; import java.io.PrintWriter; @@ -225,13 +226,16 @@ class KeyguardController { if (keyguardShowing) { state.mDismissalRequested = false; } - if (goingAwayRemoved) { - // Keyguard dismiss is canceled. Send a transition to undo the changes and clean up - // before holding the sleep token again. + if (goingAwayRemoved || (keyguardShowing && Flags.keyguardAppearTransition())) { + // Keyguard decided to show or stopped going away. Send a transition to animate back + // to the locked state before holding the sleep token again final DisplayContent dc = mRootWindowContainer.getDefaultDisplay(); dc.requestTransitionAndLegacyPrepare( TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING); - mWindowManager.executeAppTransition(); + if (Flags.keyguardAppearTransition()) { + dc.mWallpaperController.adjustWallpaperWindows(); + } + dc.executeAppTransition(); } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index c8cb934783ef..59bda54eb089 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -857,10 +857,6 @@ class WallpaperController { } public void updateWallpaperTokens(boolean keyguardLocked) { - if (DEBUG_WALLPAPER) { - Slog.v(TAG, "Wallpaper vis: target " + mWallpaperTarget + " prev=" - + mPrevWallpaperTarget); - } updateWallpaperTokens(mWallpaperTarget != null || mPrevWallpaperTarget != null, keyguardLocked); } @@ -869,6 +865,8 @@ class WallpaperController { * Change the visibility of the top wallpaper to {@param visibility} and hide all the others. */ private void updateWallpaperTokens(boolean visibility, boolean keyguardLocked) { + ProtoLog.v(WM_DEBUG_WALLPAPER, "updateWallpaperTokens requestedVisibility=%b on" + + " keyguardLocked=%b", visibility, keyguardLocked); WindowState topWallpaper = mFindResults.getTopWallpaper(keyguardLocked); WallpaperWindowToken topWallpaperToken = topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken(); diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 55eeaf22cca8..5c24eee63317 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -274,6 +274,12 @@ class WallpaperWindowToken extends WindowToken { } @Override + boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) { + // TODO(b/233286785): Support sync state for wallpaper. See WindowState#prepareSync. + return !mVisibleRequested || !hasVisibleNotDrawnWallpaper(); + } + + @Override public String toString() { if (stringName == null) { StringBuilder sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index aeefc34f2cad..8a68afbd501f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3685,12 +3685,6 @@ public class WindowManagerService extends IWindowManager.Stub // Called by window manager policy. Not exposed externally. @Override - public void switchKeyboardLayout(int deviceId, int direction) { - mInputManager.switchKeyboardLayout(deviceId, direction); - } - - // Called by window manager policy. Not exposed externally. - @Override public void shutdown(boolean confirm) { // Pass in the UI context, since ShutdownThread requires it (to show UI). ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(), diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 6ac2774941e1..aac567c2e455 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -202,6 +202,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Whether this process has ever started a service with the BIND_INPUT_METHOD permission. private volatile boolean mHasImeService; + /** + * Whether this process can use realtime prioirity (SCHED_FIFO) for its UI and render threads + * when this process is SCHED_GROUP_TOP_APP. + */ + private final boolean mUseFifoUiScheduling; + /** Whether {@link #mActivities} is not empty. */ private volatile boolean mHasActivities; /** All activities running in the process (exclude destroying). */ @@ -340,6 +346,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs. mIsActivityConfigOverrideAllowed = false; } + mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses() + && (isSysUiPackage || mAtm.isCallerRecents(uid)); mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid, @@ -1901,6 +1909,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } + /** Returns {@code true} if the process prefers to use fifo scheduling. */ + public boolean useFifoUiScheduling() { + return mUseFifoUiScheduling; + } + @HotPath(caller = HotPath.OOM_ADJUSTMENT) public void onTopProcChanged() { if (mAtm.mVrController.isInterestingToSchedGroup()) { @@ -2078,6 +2091,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } pw.println(); } + if (mUseFifoUiScheduling) { + pw.println(prefix + " mUseFifoUiScheduling=true"); + } final int stateFlags = mActivityStateFlags; if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index a7430e563904..419bcb8650a7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -38,6 +38,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.am.ActivityManagerInternalTest.CustomThread; import static com.android.server.am.ActivityManagerService.Injector; @@ -692,6 +693,31 @@ public class ActivityManagerServiceTest { assertEquals(uid, -1); } + @SuppressWarnings("GuardedBy") + @Test + public void testFifoSwitch() { + addUidRecord(TEST_UID, TEST_PACKAGE); + final ProcessRecord fifoProc = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID); + final var wpc = fifoProc.getWindowProcessController(); + spyOn(wpc); + doReturn(true).when(wpc).useFifoUiScheduling(); + fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats); + assertTrue(fifoProc.useFifoUiScheduling()); + assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc)); + + // If there is a request to use more CPU resource (e.g. camera), the current fifo process + // should switch the capability of using fifo. + final UidRecord uidRecord = addUidRecord(TEST_UID + 1, TEST_PACKAGE + 1); + uidRecord.setCurProcState(PROCESS_STATE_TOP); + mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), false /* allowSpecifiedFifo */); + assertFalse(fifoProc.useFifoUiScheduling()); + mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), true /* allowSpecifiedFifo */); + assertTrue(fifoProc.useFifoUiScheduling()); + + fifoProc.makeInactive(mAms.mProcessStats); + assertFalse(mAms.mSpecifiedFifoProcesses.contains(fifoProc)); + } + @Test public void testGlobalIsolatedUidAllocator() { final IsolatedUidRange globalUidRange = mAms.mProcessList.mGlobalIsolatedUids; diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 52df010bd588..eac99298559e 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -85,7 +85,6 @@ import android.os.VibratorInfo; import android.os.test.TestLooper; import android.service.dreams.DreamManagerInternal; import android.telecom.TelecomManager; -import android.util.FeatureFlagUtils; import android.view.Display; import android.view.InputDevice; import android.view.KeyEvent; @@ -743,15 +742,8 @@ class TestPhoneWindowManager { void assertSwitchKeyboardLayout(int direction, int displayId) { mTestLooper.dispatchAll(); - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { - verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction), - eq(displayId), eq(mImeTargetWindowToken)); - verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt()); - } else { - verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction)); - verify(mInputMethodManagerInternal, never()) - .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any()); - } + verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction), + eq(displayId), eq(mImeTargetWindowToken)); } void assertTakeBugreport() { diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt index 511c94849681..13902184ac6e 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt @@ -17,8 +17,12 @@ package com.android.server.wm.flicker.activityembedding.rotation import android.platform.test.annotations.Presubmit +import android.tools.Position +import android.tools.datatypes.Rect import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.Condition +import android.tools.traces.DeviceStateDump import android.tools.traces.component.ComponentNameMatcher import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase import com.android.server.wm.flicker.helpers.setRotation @@ -30,7 +34,14 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin override val transition: FlickerBuilder.() -> Unit = { setup { this.setRotation(flicker.scenario.startRotation) } teardown { testApp.exit(wmHelper) } - transitions { this.setRotation(flicker.scenario.endRotation) } + transitions { + this.setRotation(flicker.scenario.endRotation) + if (!flicker.scenario.isTablet) { + wmHelper.StateSyncBuilder() + .add(navBarInPosition(flicker.scenario.isGesturalNavigation)) + .waitForAndVerify() + } + } } /** {@inheritDoc} */ @@ -76,4 +87,37 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin appLayerRotates_StartingPos() appLayerRotates_EndingPos() } + + private fun navBarInPosition(isGesturalNavigation: Boolean): Condition<DeviceStateDump> { + return Condition("navBarPosition") { dump -> + val display = + dump.layerState.displays.filterNot { it.isOff }.minByOrNull { it.id } + ?: error("There is no display!") + val displayArea = display.layerStackSpace + val navBarPosition = display.navBarPosition(isGesturalNavigation) + val navBarRegion = dump.layerState + .getLayerWithBuffer(ComponentNameMatcher.NAV_BAR) + ?.visibleRegion?.bounds ?: Rect.EMPTY + + when (navBarPosition) { + Position.TOP -> + navBarRegion.top == displayArea.top && + navBarRegion.left == displayArea.left && + navBarRegion.right == displayArea.right + Position.BOTTOM -> + navBarRegion.bottom == displayArea.bottom && + navBarRegion.left == displayArea.left && + navBarRegion.right == displayArea.right + Position.LEFT -> + navBarRegion.left == displayArea.left && + navBarRegion.top == displayArea.top && + navBarRegion.bottom == displayArea.bottom + Position.RIGHT -> + navBarRegion.right == displayArea.right && + navBarRegion.top == displayArea.top && + navBarRegion.bottom == displayArea.bottom + else -> error("Unknown position $navBarPosition") + } + } + } } diff --git a/tests/FlickerTests/README.md b/tests/FlickerTests/README.md index 6b28fdf8a8ef..7429250f5cc0 100644 --- a/tests/FlickerTests/README.md +++ b/tests/FlickerTests/README.md @@ -7,82 +7,17 @@ The tests are organized in packages according to the transitions they test (e.g. ## Adding a Test -By default tests should inherit from `RotationTestBase` or `NonRotationTestBase` and must override the variable `transitionToRun` (Kotlin) or the function `getTransitionToRun()` (Java). -Only tests that are not supported by these classes should inherit directly from the `FlickerTestBase` class. +By default, tests should inherit from `TestBase` and override the variable `transition` (Kotlin) or the function `getTransition()` (Java). -### Rotation animations and transitions +Inheriting from this class ensures the common assertions will be executed, namely: -Tests that rotate the device should inherit from `RotationTestBase`. -Tests that inherit from the class automatically receive start and end rotation values. -Moreover, these tests inherit the following checks: * all regions on the screen are covered * status bar is always visible -* status bar rotates +* status bar is at the correct position at the start and end of the transition * nav bar is always visible -* nav bar is rotates +* nav bar is at the correct position at the start and end of the transition The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation. -### Non-Rotation animations and transitions +For more examples of how a test looks like check `ChangeAppRotationTest` within the `Rotation` subdirectory. -`NonRotationTestBase` was created to make it easier to write tests that do not involve rotation (e.g., `Pip`, `split screen` or `IME`). -Tests that inherit from the class are automatically executed twice: once in portrait and once in landscape mode and the assertions are checked independently. -Moreover, these tests inherit the following checks: -* all regions on the screen are covered -* status bar is always visible -* nav bar is always visible - -The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation. - -### Exceptional cases - -Tests that rotate the device should inherit from `RotationTestBase`. -This class allows the test to be freely configured and does not provide any assertions. - - -### Example - -Start by defining common or error prone transitions using `TransitionRunner`. -```kotlin -@LargeTest -@RunWith(Parameterized::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -class MyTest( - beginRotationName: String, - beginRotation: Int -) : NonRotationTestBase(beginRotationName, beginRotation) { - init { - mTestApp = MyAppHelper(InstrumentationRegistry.getInstrumentation()) - } - - override val transitionToRun: TransitionRunner - get() = TransitionRunner.newBuilder() - .withTag("myTest") - .recordAllRuns() - .runBefore { device.pressHome() } - .runBefore { device.waitForIdle() } - .run { testApp.open() } - .runAfter{ testApp.exit() } - .repeat(2) - .includeJankyRuns() - .build() - - @Test - fun myWMTest() { - checkResults { - WmTraceSubject.assertThat(it) - .showsAppWindow(MyTestApp) - .forAllEntries() - } - } - - @Test - fun mySFTest() { - checkResults { - LayersTraceSubject.assertThat(it) - .showsLayer(MyTestApp) - .forAllEntries() - } - } -} -``` diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index e60764f137af..80282c309320 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -33,7 +33,6 @@ import android.icu.util.ULocale import android.os.Bundle import android.os.test.TestLooper import android.platform.test.annotations.Presubmit -import android.provider.Settings import android.util.proto.ProtoOutputStream import android.view.InputDevice import android.view.inputmethod.InputMethodInfo @@ -47,9 +46,7 @@ import com.android.test.input.R import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals -import org.junit.Assert.assertNull import org.junit.Assert.assertTrue -import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Rule import org.junit.Test @@ -234,631 +231,326 @@ class KeyboardLayoutManagerTests { } @Test - fun testDefaultUi_getKeyboardLayouts() { - NewSettingsApiFlag(false).use { - val keyboardLayouts = keyboardLayoutManager.keyboardLayouts - assertNotEquals( - "Default UI: Keyboard layout API should not return empty array", - 0, - keyboardLayouts.size - ) - assertTrue( - "Default UI: Keyboard layout API should provide English(US) layout", - hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) - ) - } + fun testGetKeyboardLayouts() { + val keyboardLayouts = keyboardLayoutManager.keyboardLayouts + assertNotEquals( + "Keyboard layout API should not return empty array", + 0, + keyboardLayouts.size + ) + assertTrue( + "Keyboard layout API should provide English(US) layout", + hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) + ) } @Test - fun testNewUi_getKeyboardLayouts() { - NewSettingsApiFlag(true).use { - val keyboardLayouts = keyboardLayoutManager.keyboardLayouts - assertNotEquals( - "New UI: Keyboard layout API should not return empty array", - 0, - keyboardLayouts.size - ) - assertTrue( - "New UI: Keyboard layout API should provide English(US) layout", - hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) - ) - } + fun testGetKeyboardLayout() { + val keyboardLayout = + keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR) + assertEquals("getKeyboardLayout API should return correct Layout from " + + "available layouts", + ENGLISH_US_LAYOUT_DESCRIPTOR, + keyboardLayout!!.descriptor + ) } @Test - fun testDefaultUi_getKeyboardLayoutsForInputDevice() { - NewSettingsApiFlag(false).use { - val keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier) - assertNotEquals( - "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array", - 0, - keyboardLayouts.size - ) - assertTrue( - "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " + - "layout", - hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) - ) + fun testGetSetKeyboardLayoutForInputDevice_withImeInfo() { + val imeSubtype = createImeSubtype() - val vendorSpecificKeyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutsForInputDevice( - vendorSpecificKeyboardDevice.identifier - ) - assertEquals( - "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " + - "specific layout", - 1, - vendorSpecificKeyboardLayouts.size - ) - assertEquals( - "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " + - "layout", - VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR, - vendorSpecificKeyboardLayouts[0].descriptor + keyboardLayoutManager.setKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, + ENGLISH_UK_LAYOUT_DESCRIPTOR + ) + var result = + keyboardLayoutManager.getKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) - } - } + assertEquals( + "getKeyboardLayoutForInputDevice API should return the set layout", + ENGLISH_UK_LAYOUT_DESCRIPTOR, + result.layoutDescriptor + ) - @Test - fun testNewUi_getKeyboardLayoutsForInputDevice() { - NewSettingsApiFlag(true).use { - val keyboardLayouts = keyboardLayoutManager.keyboardLayouts - assertNotEquals( - "New UI: getKeyboardLayoutsForInputDevice API should not return empty array", - 0, - keyboardLayouts.size - ) - assertTrue( - "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " + - "layout", - hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) + // This should replace previously set layout + keyboardLayoutManager.setKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, + ENGLISH_US_LAYOUT_DESCRIPTOR + ) + result = + keyboardLayoutManager.getKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) - } + assertEquals( + "getKeyboardLayoutForInputDevice API should return the last set layout", + ENGLISH_US_LAYOUT_DESCRIPTOR, + result.layoutDescriptor + ) } @Test - fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() { - NewSettingsApiFlag(false).use { - assertNull( - "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " + - "nothing was set", - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - - keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier, - ENGLISH_US_LAYOUT_DESCRIPTOR - ) - val keyboardLayout = - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - assertEquals( - "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " + - "layout", - ENGLISH_US_LAYOUT_DESCRIPTOR, - keyboardLayout + fun testGetKeyboardLayoutListForInputDevice() { + // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts + var keyboardLayouts = + keyboardLayoutManager.getKeyboardLayoutListForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, + createImeSubtypeForLanguageTag("hi-Latn") + ) + assertNotEquals( + "getKeyboardLayoutListForInputDevice API should return the list of " + + "supported layouts with matching script code", + 0, + keyboardLayouts.size + ) + assertTrue("getKeyboardLayoutListForInputDevice API should return a list " + + "containing English(US) layout for hi-Latn", + containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) + ) + assertTrue("getKeyboardLayoutListForInputDevice API should return a list " + + "containing English(No script code) layout for hi-Latn", + containsLayout( + keyboardLayouts, + createLayoutDescriptor("keyboard_layout_english_without_script_code") ) - } - } + ) - @Test - fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() { - NewSettingsApiFlag(true).use { - keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier, - ENGLISH_US_LAYOUT_DESCRIPTOR + // Check Layouts for "hi" which by default uses 'Deva' script. + keyboardLayouts = + keyboardLayoutManager.getKeyboardLayoutListForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, + createImeSubtypeForLanguageTag("hi") ) - assertNull( - "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " + - "even after setCurrentKeyboardLayoutForInputDevice", - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - } - } + assertEquals("getKeyboardLayoutListForInputDevice API should return empty " + + "list if no supported layouts available", + 0, + keyboardLayouts.size + ) - @Test - fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() { - NewSettingsApiFlag(false).use { - keyboardLayoutManager.addKeyboardLayoutForInputDevice( - keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR + // If user manually selected some layout, always provide it in the layout list + val imeSubtype = createImeSubtypeForLanguageTag("hi") + keyboardLayoutManager.setKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, + ENGLISH_US_LAYOUT_DESCRIPTOR + ) + keyboardLayouts = + keyboardLayoutManager.getKeyboardLayoutListForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, + imeSubtype ) - - val keyboardLayouts = - keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice( - keyboardDevice.identifier - ) - assertEquals( - "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " + - "layout", + assertEquals("getKeyboardLayoutListForInputDevice API should return user " + + "selected layout even if the script is incompatible with IME", 1, - keyboardLayouts.size - ) - assertEquals( - "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " + - "English(US) layout", - ENGLISH_US_LAYOUT_DESCRIPTOR, - keyboardLayouts[0] - ) - assertEquals( - "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " + - "English(US) layout (Auto select the first enabled layout)", - ENGLISH_US_LAYOUT_DESCRIPTOR, - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - - keyboardLayoutManager.removeKeyboardLayoutForInputDevice( - keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR - ) - assertEquals( - "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts", - 0, - keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice( - keyboardDevice.identifier - ).size - ) - assertNull( - "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " + - "the enabled layout is removed", - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - } - } - - @Test - fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() { - NewSettingsApiFlag(true).use { - keyboardLayoutManager.addKeyboardLayoutForInputDevice( - keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR - ) - - assertEquals( - "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " + - "an empty array", - 0, - keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice( - keyboardDevice.identifier - ).size - ) - assertNull( - "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null", - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - } - } - - @Test - fun testDefaultUi_switchKeyboardLayout() { - NewSettingsApiFlag(false).use { - keyboardLayoutManager.addKeyboardLayoutForInputDevice( - keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR - ) - keyboardLayoutManager.addKeyboardLayoutForInputDevice( - keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR - ) - assertEquals( - "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " + - "English(US) layout", - ENGLISH_US_LAYOUT_DESCRIPTOR, - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - - keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1) - - // Throws null pointer because trying to show toast using TestLooper - assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() } - assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " + - "English(UK) layout", - ENGLISH_UK_LAYOUT_DESCRIPTOR, - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - } - } - - @Test - fun testNewUi_switchKeyboardLayout() { - NewSettingsApiFlag(true).use { - keyboardLayoutManager.addKeyboardLayoutForInputDevice( - keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR - ) - keyboardLayoutManager.addKeyboardLayoutForInputDevice( - keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR - ) - - keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1) - testLooper.dispatchAll() - - assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " + - "null", - keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice( - keyboardDevice.identifier - ) - ) - } - } - - @Test - fun testDefaultUi_getKeyboardLayout() { - NewSettingsApiFlag(false).use { - val keyboardLayout = - keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR) - assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " + - "available layouts", - ENGLISH_US_LAYOUT_DESCRIPTOR, - keyboardLayout!!.descriptor - ) - } - } - - @Test - fun testNewUi_getKeyboardLayout() { - NewSettingsApiFlag(true).use { - val keyboardLayout = - keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR) - assertEquals("New UI: getKeyboardLayout API should return correct Layout from " + - "available layouts", - ENGLISH_US_LAYOUT_DESCRIPTOR, - keyboardLayout!!.descriptor - ) - } - } - - @Test - fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() { - NewSettingsApiFlag(false).use { - val imeSubtype = createImeSubtype() - keyboardLayoutManager.setKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, - ENGLISH_UK_LAYOUT_DESCRIPTOR - ) - assertEquals( - "Default UI: getKeyboardLayoutForInputDevice API should always return " + - "KeyboardLayoutSelectionResult.FAILED", - KeyboardLayoutSelectionResult.FAILED, - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype - ) - ) - } - } - - @Test - fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() { - NewSettingsApiFlag(true).use { - val imeSubtype = createImeSubtype() - - keyboardLayoutManager.setKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, - ENGLISH_UK_LAYOUT_DESCRIPTOR - ) - var result = - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype - ) - assertEquals( - "New UI: getKeyboardLayoutForInputDevice API should return the set layout", - ENGLISH_UK_LAYOUT_DESCRIPTOR, - result.layoutDescriptor - ) - - // This should replace previously set layout - keyboardLayoutManager.setKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, - ENGLISH_US_LAYOUT_DESCRIPTOR - ) - result = - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype - ) - assertEquals( - "New UI: getKeyboardLayoutForInputDevice API should return the last set layout", - ENGLISH_US_LAYOUT_DESCRIPTOR, - result.layoutDescriptor - ) - } - } - - @Test - fun testDefaultUi_getKeyboardLayoutListForInputDevice() { - NewSettingsApiFlag(false).use { - val keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutListForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtype() - ) - assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " + - "return empty array", - 0, - keyboardLayouts.size - ) - } - } + keyboardLayouts.size + ) - @Test - fun testNewUi_getKeyboardLayoutListForInputDevice() { - NewSettingsApiFlag(true).use { - // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts - var keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutListForInputDevice( + // Special case Japanese: UScript ignores provided script code for certain language tags + // Should manually match provided script codes and then rely on Uscript to derive + // script from language tags and match those. + keyboardLayouts = + keyboardLayoutManager.getKeyboardLayoutListForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtypeForLanguageTag("hi-Latn") - ) - assertNotEquals( - "New UI: getKeyboardLayoutListForInputDevice API should return the list of " + - "supported layouts with matching script code", - 0, - keyboardLayouts.size - ) - assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " + - "containing English(US) layout for hi-Latn", - containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) + createImeSubtypeForLanguageTag("ja-Latn-JP") ) - assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " + - "containing English(No script code) layout for hi-Latn", - containsLayout( - keyboardLayouts, - createLayoutDescriptor("keyboard_layout_english_without_script_code") - ) + assertNotEquals( + "getKeyboardLayoutListForInputDevice API should return the list of " + + "supported layouts with matching script code for ja-Latn-JP", + 0, + keyboardLayouts.size + ) + assertTrue("getKeyboardLayoutListForInputDevice API should return a list " + + "containing English(US) layout for ja-Latn-JP", + containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) + ) + assertTrue("getKeyboardLayoutListForInputDevice API should return a list " + + "containing English(No script code) layout for ja-Latn-JP", + containsLayout( + keyboardLayouts, + createLayoutDescriptor("keyboard_layout_english_without_script_code") ) + ) - // Check Layouts for "hi" which by default uses 'Deva' script. - keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutListForInputDevice( + // If script code not explicitly provided for Japanese should rely on Uscript to find + // derived script code and hence no suitable layout will be found. + keyboardLayouts = + keyboardLayoutManager.getKeyboardLayoutListForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtypeForLanguageTag("hi") - ) - assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " + - "list if no supported layouts available", - 0, - keyboardLayouts.size - ) - - // If user manually selected some layout, always provide it in the layout list - val imeSubtype = createImeSubtypeForLanguageTag("hi") - keyboardLayoutManager.setKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, - ENGLISH_US_LAYOUT_DESCRIPTOR - ) - keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutListForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, - imeSubtype - ) - assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " + - "selected layout even if the script is incompatible with IME", - 1, - keyboardLayouts.size - ) - - // Special case Japanese: UScript ignores provided script code for certain language tags - // Should manually match provided script codes and then rely on Uscript to derive - // script from language tags and match those. - keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutListForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtypeForLanguageTag("ja-Latn-JP") - ) - assertNotEquals( - "New UI: getKeyboardLayoutListForInputDevice API should return the list of " + - "supported layouts with matching script code for ja-Latn-JP", - 0, - keyboardLayouts.size - ) - assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " + - "containing English(US) layout for ja-Latn-JP", - containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) - ) - assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " + - "containing English(No script code) layout for ja-Latn-JP", - containsLayout( - keyboardLayouts, - createLayoutDescriptor("keyboard_layout_english_without_script_code") - ) - ) - - // If script code not explicitly provided for Japanese should rely on Uscript to find - // derived script code and hence no suitable layout will be found. - keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutListForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtypeForLanguageTag("ja-JP") - ) - assertEquals( - "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " + - "supported layouts with matching script code for ja-JP", - 0, - keyboardLayouts.size - ) - - // If IME doesn't have a corresponding language tag, then should show all available - // layouts no matter the script code. - keyboardLayouts = - keyboardLayoutManager.getKeyboardLayoutListForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, null - ) - assertNotEquals( - "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" + - "language tag or subtype not provided", - 0, - keyboardLayouts.size - ) - assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " + - "layouts if language tag or subtype not provided", - containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) + createImeSubtypeForLanguageTag("ja-JP") ) - assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " + - "layouts if language tag or subtype not provided", - containsLayout( - keyboardLayouts, - createLayoutDescriptor("keyboard_layout_russian") - ) - ) - } - } + assertEquals( + "getKeyboardLayoutListForInputDevice API should return empty list of " + + "supported layouts with matching script code for ja-JP", + 0, + keyboardLayouts.size + ) - @Test - fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() { - NewSettingsApiFlag(true).use { - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTag("en-US"), - ENGLISH_US_LAYOUT_DESCRIPTOR - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTag("en-GB"), - ENGLISH_UK_LAYOUT_DESCRIPTOR - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTag("de"), - GERMAN_LAYOUT_DESCRIPTOR - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTag("fr-FR"), - createLayoutDescriptor("keyboard_layout_french") - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTag("ru"), + // If IME doesn't have a corresponding language tag, then should show all available + // layouts no matter the script code. + keyboardLayouts = + keyboardLayoutManager.getKeyboardLayoutListForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, null + ) + assertNotEquals( + "getKeyboardLayoutListForInputDevice API should return all layouts if" + + "language tag or subtype not provided", + 0, + keyboardLayouts.size + ) + assertTrue("getKeyboardLayoutListForInputDevice API should contain Latin " + + "layouts if language tag or subtype not provided", + containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR) + ) + assertTrue("getKeyboardLayoutListForInputDevice API should contain Cyrillic " + + "layouts if language tag or subtype not provided", + containsLayout( + keyboardLayouts, createLayoutDescriptor("keyboard_layout_russian") ) - assertEquals( - "New UI: getDefaultKeyboardLayoutForInputDevice should return " + - "KeyboardLayoutSelectionResult.FAILED when no layout available", - KeyboardLayoutSelectionResult.FAILED, - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtypeForLanguageTag("it") - ) - ) - assertEquals( - "New UI: getDefaultKeyboardLayoutForInputDevice should return " + - "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" + - "available", - KeyboardLayoutSelectionResult.FAILED, - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtypeForLanguageTag("en-Deva") - ) - ) - } + ) } @Test - fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() { - NewSettingsApiFlag(true).use { - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"), - ENGLISH_US_LAYOUT_DESCRIPTOR - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"), - createLayoutDescriptor("keyboard_layout_english_us_dvorak") - ) - // Try to match layout type even if country doesn't match - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"), - createLayoutDescriptor("keyboard_layout_english_us_dvorak") - ) - // Choose layout based on layout type priority, if layout type is not provided by IME - // (Qwerty > Dvorak > Extended) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("en-US", ""), - ENGLISH_US_LAYOUT_DESCRIPTOR - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"), - ENGLISH_UK_LAYOUT_DESCRIPTOR - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"), - GERMAN_LAYOUT_DESCRIPTOR - ) - // Wrong layout type should match with language if provided layout type not available - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"), - GERMAN_LAYOUT_DESCRIPTOR - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"), - createLayoutDescriptor("keyboard_layout_french") - ) - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"), - createLayoutDescriptor("keyboard_layout_russian_qwerty") - ) - // If layout type is empty then prioritize KCM with empty layout type - assertCorrectLayout( - keyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("ru", ""), - createLayoutDescriptor("keyboard_layout_russian") + fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() { + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTag("en-US"), + ENGLISH_US_LAYOUT_DESCRIPTOR + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTag("en-GB"), + ENGLISH_UK_LAYOUT_DESCRIPTOR + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTag("de"), + GERMAN_LAYOUT_DESCRIPTOR + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTag("fr-FR"), + createLayoutDescriptor("keyboard_layout_french") + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTag("ru"), + createLayoutDescriptor("keyboard_layout_russian") + ) + assertEquals( + "getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout available", + KeyboardLayoutSelectionResult.FAILED, + keyboardLayoutManager.getKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, + createImeSubtypeForLanguageTag("it") ) - assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " + + ) + assertEquals( + "getDefaultKeyboardLayoutForInputDevice should return " + "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" + "available", - KeyboardLayoutSelectionResult.FAILED, - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - keyboardDevice.identifier, USER_ID, imeInfo, - createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "") - ) + KeyboardLayoutSelectionResult.FAILED, + keyboardLayoutManager.getKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, + createImeSubtypeForLanguageTag("en-Deva") ) - } + ) } @Test - fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() { - NewSettingsApiFlag(true).use { - val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty") - // Should return English dvorak even if IME current layout is French, since HW says the - // keyboard is a Dvorak keyboard - assertCorrectLayout( - englishDvorakKeyboardDevice, - frenchSubtype, - createLayoutDescriptor("keyboard_layout_english_us_dvorak") + fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() { + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"), + ENGLISH_US_LAYOUT_DESCRIPTOR + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"), + createLayoutDescriptor("keyboard_layout_english_us_dvorak") + ) + // Try to match layout type even if country doesn't match + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"), + createLayoutDescriptor("keyboard_layout_english_us_dvorak") + ) + // Choose layout based on layout type priority, if layout type is not provided by IME + // (Qwerty > Dvorak > Extended) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("en-US", ""), + ENGLISH_US_LAYOUT_DESCRIPTOR + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"), + ENGLISH_UK_LAYOUT_DESCRIPTOR + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"), + GERMAN_LAYOUT_DESCRIPTOR + ) + // Wrong layout type should match with language if provided layout type not available + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"), + GERMAN_LAYOUT_DESCRIPTOR + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"), + createLayoutDescriptor("keyboard_layout_french") + ) + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"), + createLayoutDescriptor("keyboard_layout_russian_qwerty") + ) + // If layout type is empty then prioritize KCM with empty layout type + assertCorrectLayout( + keyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("ru", ""), + createLayoutDescriptor("keyboard_layout_russian") + ) + assertEquals("getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" + + "available", + KeyboardLayoutSelectionResult.FAILED, + keyboardLayoutManager.getKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, + createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "") ) + ) + } - // Back to back changing HW keyboards with same product and vendor ID but different - // language and layout type should configure the layouts correctly. - assertCorrectLayout( - englishQwertyKeyboardDevice, - frenchSubtype, - createLayoutDescriptor("keyboard_layout_english_us") - ) + @Test + fun testGetDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() { + val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty") + // Should return English dvorak even if IME current layout is French, since HW says the + // keyboard is a Dvorak keyboard + assertCorrectLayout( + englishDvorakKeyboardDevice, + frenchSubtype, + createLayoutDescriptor("keyboard_layout_english_us_dvorak") + ) - // Fallback to IME information if the HW provided layout script is incompatible with the - // provided IME subtype - assertCorrectLayout( - englishDvorakKeyboardDevice, - createImeSubtypeForLanguageTagAndLayoutType("ru", ""), - createLayoutDescriptor("keyboard_layout_russian") - ) - } + // Back to back changing HW keyboards with same product and vendor ID but different + // language and layout type should configure the layouts correctly. + assertCorrectLayout( + englishQwertyKeyboardDevice, + frenchSubtype, + createLayoutDescriptor("keyboard_layout_english_us") + ) + + // Fallback to IME information if the HW provided layout script is incompatible with the + // provided IME subtype + assertCorrectLayout( + englishDvorakKeyboardDevice, + createImeSubtypeForLanguageTagAndLayoutType("ru", ""), + createLayoutDescriptor("keyboard_layout_russian") + ) } @Test @@ -867,27 +559,25 @@ class KeyboardLayoutManagerTests { KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz"))) Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping - NewSettingsApiFlag(true).use { - keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id) - ExtendedMockito.verify { - FrameworkStatsLog.write( - ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), - ArgumentMatchers.anyBoolean(), - ArgumentMatchers.eq(keyboardDevice.vendorId), - ArgumentMatchers.eq(keyboardDevice.productId), - ArgumentMatchers.eq( - createByteArray( - KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, - LAYOUT_TYPE_DEFAULT, - GERMAN_LAYOUT_NAME, - KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, - "de-Latn", - LAYOUT_TYPE_QWERTZ - ), + keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id) + ExtendedMockito.verify { + FrameworkStatsLog.write( + ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.eq(keyboardDevice.vendorId), + ArgumentMatchers.eq(keyboardDevice.productId), + ArgumentMatchers.eq( + createByteArray( + KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, + LAYOUT_TYPE_DEFAULT, + GERMAN_LAYOUT_NAME, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, + "de-Latn", + LAYOUT_TYPE_QWERTZ ), - ArgumentMatchers.eq(keyboardDevice.deviceBus), - ) - } + ), + ArgumentMatchers.eq(keyboardDevice.deviceBus), + ) } } @@ -897,27 +587,25 @@ class KeyboardLayoutManagerTests { KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz"))) Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping - NewSettingsApiFlag(true).use { - keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id) - ExtendedMockito.verify { - FrameworkStatsLog.write( - ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), - ArgumentMatchers.anyBoolean(), - ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId), - ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId), - ArgumentMatchers.eq( - createByteArray( - "en", - LAYOUT_TYPE_QWERTY, - ENGLISH_US_LAYOUT_NAME, - KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, - "de-Latn", - LAYOUT_TYPE_QWERTZ - ) - ), - ArgumentMatchers.eq(keyboardDevice.deviceBus), - ) - } + keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id) + ExtendedMockito.verify { + FrameworkStatsLog.write( + ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId), + ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId), + ArgumentMatchers.eq( + createByteArray( + "en", + LAYOUT_TYPE_QWERTY, + ENGLISH_US_LAYOUT_NAME, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, + "de-Latn", + LAYOUT_TYPE_QWERTZ + ) + ), + ArgumentMatchers.eq(keyboardDevice.deviceBus), + ) } } @@ -925,27 +613,25 @@ class KeyboardLayoutManagerTests { fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() { val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype())) Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping - NewSettingsApiFlag(true).use { - keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id) - ExtendedMockito.verify { - FrameworkStatsLog.write( - ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), - ArgumentMatchers.anyBoolean(), - ArgumentMatchers.eq(keyboardDevice.vendorId), - ArgumentMatchers.eq(keyboardDevice.productId), - ArgumentMatchers.eq( - createByteArray( - KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, - LAYOUT_TYPE_DEFAULT, - "Default", - KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT, - KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, - LAYOUT_TYPE_DEFAULT - ), + keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id) + ExtendedMockito.verify { + FrameworkStatsLog.write( + ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.eq(keyboardDevice.vendorId), + ArgumentMatchers.eq(keyboardDevice.productId), + ArgumentMatchers.eq( + createByteArray( + KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, + LAYOUT_TYPE_DEFAULT, + "Default", + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT, + KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, + LAYOUT_TYPE_DEFAULT ), - ArgumentMatchers.eq(keyboardDevice.deviceBus), - ) - } + ), + ArgumentMatchers.eq(keyboardDevice.deviceBus), + ) } } @@ -953,19 +639,17 @@ class KeyboardLayoutManagerTests { fun testConfigurationNotLogged_onInputDeviceChanged() { val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype())) Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping - NewSettingsApiFlag(true).use { - keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) - ExtendedMockito.verify({ - FrameworkStatsLog.write( - ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), - ArgumentMatchers.anyBoolean(), - ArgumentMatchers.anyInt(), - ArgumentMatchers.anyInt(), - ArgumentMatchers.any(ByteArray::class.java), - ArgumentMatchers.anyInt(), - ) - }, Mockito.times(0)) - } + keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) + ExtendedMockito.verify({ + FrameworkStatsLog.write( + ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any(ByteArray::class.java), + ArgumentMatchers.anyInt(), + ) + }, Mockito.times(0)) } @Test @@ -975,18 +659,16 @@ class KeyboardLayoutManagerTests { Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice( ArgumentMatchers.eq(keyboardDevice.id) ) - NewSettingsApiFlag(true).use { - keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) - ExtendedMockito.verify( - notificationManager, - Mockito.times(1) - ).notifyAsUser( - ArgumentMatchers.isNull(), - ArgumentMatchers.anyInt(), - ArgumentMatchers.any(), - ArgumentMatchers.any() - ) - } + keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) + ExtendedMockito.verify( + notificationManager, + Mockito.times(1) + ).notifyAsUser( + ArgumentMatchers.isNull(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any(), + ArgumentMatchers.any() + ) } @Test @@ -996,18 +678,16 @@ class KeyboardLayoutManagerTests { Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice( ArgumentMatchers.eq(keyboardDevice.id) ) - NewSettingsApiFlag(true).use { - keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) - ExtendedMockito.verify( - notificationManager, - Mockito.never() - ).notifyAsUser( - ArgumentMatchers.isNull(), - ArgumentMatchers.anyInt(), - ArgumentMatchers.any(), - ArgumentMatchers.any() - ) - } + keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) + ExtendedMockito.verify( + notificationManager, + Mockito.never() + ).notifyAsUser( + ArgumentMatchers.isNull(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any(), + ArgumentMatchers.any() + ) } private fun assertCorrectLayout( @@ -1019,7 +699,7 @@ class KeyboardLayoutManagerTests { device.identifier, USER_ID, imeInfo, imeSubtype ) assertEquals( - "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout", + "getDefaultKeyboardLayoutForInputDevice should return $expectedLayout", expectedLayout, result.layoutDescriptor ) @@ -1123,21 +803,4 @@ class KeyboardLayoutManagerTests { info.serviceInfo.name = RECEIVER_NAME return info } - - private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable { - init { - Settings.Global.putString( - context.contentResolver, - "settings_new_keyboard_ui", enabled.toString() - ) - } - - override fun close() { - Settings.Global.putString( - context.contentResolver, - "settings_new_keyboard_ui", - "" - ) - } - } } |