diff options
273 files changed, 6515 insertions, 2325 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 1667f2e4457a..5d134c6fed7b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -33228,6 +33228,7 @@ package android.os { } public interface IBinder { + method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException; method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException; @@ -33236,6 +33237,7 @@ package android.os { method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException; method public boolean pingBinder(); method @Nullable public android.os.IInterface queryLocalInterface(@NonNull String); + method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default boolean removeFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback); method public boolean transact(int, @NonNull android.os.Parcel, @Nullable android.os.Parcel, int) throws android.os.RemoteException; method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int); field public static final int DUMP_TRANSACTION = 1598311760; // 0x5f444d50 @@ -33253,6 +33255,12 @@ package android.os { method public default void binderDied(@NonNull android.os.IBinder); } + @FlaggedApi("android.os.binder_frozen_state_change_callback") public static interface IBinder.FrozenStateChangeCallback { + method public void onFrozenStateChanged(@NonNull android.os.IBinder, int); + field public static final int STATE_FROZEN = 0; // 0x0 + field public static final int STATE_UNFROZEN = 1; // 0x1 + } + public interface IInterface { method public android.os.IBinder asBinder(); } @@ -44070,7 +44078,7 @@ package android.telephony { } public static final class CarrierConfigManager.Gps { - field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool"; + field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config_bugfix") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool"; field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool"; field public static final String KEY_PREFIX = "gps."; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4350545a1b1d..5db79fe92345 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -8288,12 +8288,12 @@ public final class ActivityThread extends ClientTransactionHandler } Context c = null; ApplicationInfo ai = info.applicationInfo; - if (context.getPackageName().equals(ai.packageName)) { + if (context != null && context.getPackageName().equals(ai.packageName)) { c = context; } else if (mInitialApplication != null && mInitialApplication.getPackageName().equals(ai.packageName)) { c = mInitialApplication; - } else { + } else if (context != null) { try { c = context.createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 52c84dc0ac5d..26f919f99ee9 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -778,8 +778,18 @@ public class LauncherApps { public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) { logErrorForInvalidProfileAccess(user); try { - return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(), - packageName, user), user); + final List<LauncherActivityInfo> activityList = convertToActivityList( + mService.getLauncherActivities( + mContext.getPackageName(), + packageName, + user + ), user); + if (activityList.isEmpty()) { + // b/350144057 + Log.d(TAG, "getActivityList: No launchable activities found for" + + "packageName=" + packageName + ", user=" + user); + } + return activityList; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 1a309c646882..983bbc3b2774 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -119,6 +119,13 @@ flag { } flag { + namespace: "input_native" + name: "use_key_gesture_event_handler_multi_press_gestures" + description: "Use KeyGestureEvent handler APIs to control multi key press gestures" + bug: "358569822" +} + +flag { name: "keyboard_repeat_keys" namespace: "input_native" description: "Allow configurable timeout before key repeat and repeat delay rate for key repeats" diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index c22f46cdc2b5..80546cd6770f 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -650,13 +650,13 @@ public final class BinderProxy implements IBinder { * weakly referenced by JNI so the strong references here are needed to keep the callbacks * around until the proxy is GC'ed. */ - private List<IFrozenStateChangeCallback> mFrozenStateChangeCallbacks = + private List<FrozenStateChangeCallback> mFrozenStateChangeCallbacks = Collections.synchronizedList(new ArrayList<>()); /** - * See {@link IBinder#addFrozenStateChangeCallback(IFrozenStateChangeCallback)} + * See {@link IBinder#addFrozenStateChangeCallback(FrozenStateChangeCallback)} */ - public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback) + public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback) throws RemoteException { addFrozenStateChangeCallbackNative(callback); mFrozenStateChangeCallbacks.add(callback); @@ -665,16 +665,16 @@ public final class BinderProxy implements IBinder { /** * See {@link IBinder#removeFrozenStateChangeCallback} */ - public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) { + public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) { mFrozenStateChangeCallbacks.remove(callback); return removeFrozenStateChangeCallbackNative(callback); } - private native void addFrozenStateChangeCallbackNative(IFrozenStateChangeCallback callback) + private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback) throws RemoteException; private native boolean removeFrozenStateChangeCallbackNative( - IFrozenStateChangeCallback callback); + FrozenStateChangeCallback callback); /** * Perform a dump on the remote object @@ -762,10 +762,9 @@ public final class BinderProxy implements IBinder { } private static void invokeFrozenStateChangeCallback( - IFrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) { + FrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) { try { - callback.onFrozenStateChanged(binderProxy, - IFrozenStateChangeCallback.State.values()[stateIndex]); + callback.onFrozenStateChanged(binderProxy, stateIndex); } catch (RuntimeException exc) { Log.w("BinderNative", "Uncaught exception from frozen state change callback", exc); diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 8185e8e542e1..a997f4c86704 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -16,11 +16,15 @@ package android.os; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Base interface for a remotable object, the core part of a lightweight @@ -377,9 +381,24 @@ public interface IBinder { */ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags); - /** @hide */ - interface IFrozenStateChangeCallback { - enum State {FROZEN, UNFROZEN}; + /** + * A callback interface for receiving frozen state change events. + */ + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + interface FrozenStateChangeCallback { + /** + * @hide + */ + @IntDef(prefix = {"STATE_"}, value = { + STATE_FROZEN, + STATE_UNFROZEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface State { + } + + int STATE_FROZEN = 0; + int STATE_UNFROZEN = 1; /** * Interface for receiving a callback when the process hosting an IBinder @@ -387,13 +406,13 @@ public interface IBinder { * @param who The IBinder whose hosting process has changed state. * @param state The latest state. */ - void onFrozenStateChanged(@NonNull IBinder who, State state); + void onFrozenStateChanged(@NonNull IBinder who, @State int state); } /** - * {@link addFrozenStateChangeCallback} provides a callback mechanism to notify about process - * frozen/unfrozen events. Upon registration and any subsequent state changes, the callback is - * invoked with the latest process frozen state. + * This method provides a callback mechanism to notify about process frozen/unfrozen events. + * Upon registration and any subsequent state changes, the callback is invoked with the latest + * process frozen state. * * <p>If the listener process (the one using this API) is itself frozen, state change events * might be combined into a single one with the latest frozen state. This single event would @@ -410,19 +429,19 @@ public interface IBinder { * * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support * this feature. - * @hide */ - default void addFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) throws RemoteException { throw new UnsupportedOperationException(); } /** - * Unregister a {@link IFrozenStateChangeCallback}. The callback will no longer be invoked when + * Unregister a {@link FrozenStateChangeCallback}. The callback will no longer be invoked when * the hosting process changes its frozen state. - * @hide */ - default boolean removeFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) { + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + default boolean removeFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) { throw new UnsupportedOperationException(); } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index a4a7a983c44c..1ca4574e79b4 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3763,7 +3763,8 @@ public class UserManager { } private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY = - "cache_key.is_user_unlocked"; + PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked"); private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache = new PropertyInvalidatedCache<Integer, Boolean>( @@ -6694,7 +6695,9 @@ public class UserManager { } /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */ - private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props"; + private static final String CACHE_KEY_STATIC_USER_PROPERTIES = + PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "static_user_props"); private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache = new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) { @@ -6721,7 +6724,9 @@ public class UserManager { } /* Cache key for UserProperties object. */ - private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties"; + private static final String CACHE_KEY_USER_PROPERTIES = + PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "user_properties"); // TODO: It would be better to somehow have this as static, so that it can work cross-context. private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache = diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 39bd15c968d7..738d12978aed 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -153,6 +153,14 @@ flag { } flag { + name: "binder_frozen_state_change_callback" + is_exported: true + namespace: "system_performance" + description: "Guards the frozen state change callback API." + bug: "361157077" +} + +flag { name: "message_queue_tail_tracking" namespace: "system_performance" description: "track tail of message queue." diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e32625e1f7a8..0ada9934482c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -111,6 +111,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -3022,6 +3023,9 @@ public final class Settings { /** @hide - Private call() method to query the 'configuration' table */ public static final String CALL_METHOD_LIST_CONFIG = "LIST_config"; + /** @hide - Private call() method to query the 'configuration' tables' namespaces */ + public static final String CALL_METHOD_LIST_NAMESPACES_CONFIG = "LIST_namespaces_config"; + /** @hide - Private call() method to disable / re-enable syncs to the 'configuration' table */ public static final String CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG = "SET_SYNC_DISABLED_MODE_config"; @@ -20458,6 +20462,10 @@ public final class Settings { * * The keys take the form {@code namespace/flag}, and the values are the flag values. * + * Note: this API is _not_ performant, and may make a large number of + * Binder calls. It is intended for use in testing and debugging, and + * should not be used in performance-sensitive code. + * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @@ -20469,13 +20477,33 @@ public final class Settings { Bundle arg = new Bundle(); arg.putInt(Settings.CALL_METHOD_USER_KEY, resolver.getUserId()); IContentProvider cp = sProviderHolder.getProvider(resolver); - Bundle b = cp.call(resolver.getAttributionSource(), - sProviderHolder.mUri.getAuthority(), CALL_METHOD_LIST_CONFIG, null, arg); - if (b != null) { - Map<String, String> flagsToValues = - (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, - java.util.HashMap.class); - allFlags.putAll(flagsToValues); + + if (Flags.reduceBinderTransactionSizeForGetAllProperties()) { + Bundle b = cp.call(resolver.getAttributionSource(), + sProviderHolder.mUri.getAuthority(), + CALL_METHOD_LIST_NAMESPACES_CONFIG, null, arg); + if (b != null) { + HashSet<String> namespaces = + (HashSet) b.getSerializable(Settings.NameValueTable.VALUE, + java.util.HashSet.class); + for (String namespace : namespaces) { + Map<String, String> keyValues = + getStrings(namespace, new ArrayList()); + for (String key : keyValues.keySet()) { + allFlags.put(namespace + "/" + key, keyValues.get(key)); + } + } + } + } else { + Bundle b = cp.call(resolver.getAttributionSource(), + sProviderHolder.mUri.getAuthority(), + CALL_METHOD_LIST_CONFIG, null, arg); + if (b != null) { + Map<String, String> flagsToValues = + (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, + java.util.HashMap.class); + allFlags.putAll(flagsToValues); + } } } catch (RemoteException e) { Log.w(TAG, "Can't query configuration table for " + CONTENT_URI, e); diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig index 5c0f8737ca27..4c636735b5ce 100644 --- a/core/java/android/provider/flags.aconfig +++ b/core/java/android/provider/flags.aconfig @@ -52,3 +52,14 @@ flag { description: "Enable the new ContactsContract Default Account APIs." bug: "359957527" } + +flag { + name: "reduce_binder_transaction_size_for_get_all_properties" + namespace: "core_experiments_team_internal" + description: "Reduce Binder transaction size in getAllProperties calls" + bug: "362652574" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index ad457ce6e18d..384add5cf929 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -70,6 +70,7 @@ import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -370,6 +371,7 @@ public abstract class WallpaperService extends Service { private float mDefaultDimAmount = 0.05f; SurfaceControl mBbqSurfaceControl; BLASTBufferQueue mBlastBufferQueue; + IBinder mBbqApplyToken = new Binder(); private SurfaceControl mScreenshotSurfaceControl; private Point mScreenshotSize = new Point(); @@ -2390,6 +2392,7 @@ public abstract class WallpaperService extends Service { if (mBlastBufferQueue == null) { mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl, width, height, format); + mBlastBufferQueue.setApplyToken(mBbqApplyToken); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 3c61f4f5a33c..3846972a12e8 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -84,17 +84,6 @@ flag { } flag { - name: "fix_font_update_failure" - namespace: "text" - description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure." - is_fixed_read_only: true - bug: "331717791" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "fix_misaligned_context_menu" namespace: "text" description: "Fix the context menu misalignment and incosistent icon size." @@ -154,26 +143,6 @@ flag { } flag { - name: "portuguese_hyphenator" - namespace: "text" - description: "Portuguese taiored hyphenator" - bug: "344656282" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "dont_break_email_in_nobreak_tag" - namespace: "text" - description: "Prevent line break inside email." - bug: "350691716" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "handwriting_gesture_with_transformation" namespace: "text" description: "Fix handwriting gesture is not working when view has transformation." diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index f8c97eb5fa72..53935e810913 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1341,7 +1341,7 @@ public final class Display { public HdrCapabilities getHdrCapabilities() { synchronized (mLock) { updateDisplayInfoLocked(); - if (mDisplayInfo.hdrCapabilities == null) { + if (mDisplayInfo.hdrCapabilities == null || mDisplayInfo.isForceSdr) { return null; } int[] supportedHdrTypes; @@ -1363,6 +1363,7 @@ public final class Display { supportedHdrTypes[index++] = enabledType; } } + return new HdrCapabilities(supportedHdrTypes, mDisplayInfo.hdrCapabilities.mMaxLuminance, mDisplayInfo.hdrCapabilities.mMaxAverageLuminance, @@ -2087,6 +2088,7 @@ public final class Display { /** * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static String stateToString(int state) { switch (state) { case STATE_UNKNOWN: @@ -2109,6 +2111,7 @@ public final class Display { } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static String stateReasonToString(@StateReason int reason) { switch (reason) { case STATE_REASON_UNKNOWN: diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 157cec8a4d0f..cac3e3c25098 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -230,6 +230,9 @@ public final class DisplayInfo implements Parcelable { /** The formats disabled by user **/ public int[] userDisabledHdrTypes = {}; + /** When true, all HDR capabilities are disabled **/ + public boolean isForceSdr; + /** * Indicates whether the display can be switched into a mode with minimal post * processing. @@ -440,6 +443,7 @@ public final class DisplayInfo implements Parcelable { && colorMode == other.colorMode && Arrays.equals(supportedColorModes, other.supportedColorModes) && Objects.equals(hdrCapabilities, other.hdrCapabilities) + && isForceSdr == other.isForceSdr && Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes) && minimalPostProcessingSupported == other.minimalPostProcessingSupported && logicalDensityDpi == other.logicalDensityDpi @@ -502,6 +506,7 @@ public final class DisplayInfo implements Parcelable { supportedColorModes = Arrays.copyOf( other.supportedColorModes, other.supportedColorModes.length); hdrCapabilities = other.hdrCapabilities; + isForceSdr = other.isForceSdr; userDisabledHdrTypes = other.userDisabledHdrTypes; minimalPostProcessingSupported = other.minimalPostProcessingSupported; logicalDensityDpi = other.logicalDensityDpi; @@ -567,6 +572,7 @@ public final class DisplayInfo implements Parcelable { supportedColorModes[i] = source.readInt(); } hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class); + isForceSdr = source.readBoolean(); minimalPostProcessingSupported = source.readBoolean(); logicalDensityDpi = source.readInt(); physicalXDpi = source.readFloat(); @@ -636,6 +642,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(supportedColorModes[i]); } dest.writeParcelable(hdrCapabilities, flags); + dest.writeBoolean(isForceSdr); dest.writeBoolean(minimalPostProcessingSupported); dest.writeInt(logicalDensityDpi); dest.writeFloat(physicalXDpi); @@ -874,6 +881,8 @@ public final class DisplayInfo implements Parcelable { sb.append(Arrays.toString(appsSupportedModes)); sb.append(", hdrCapabilities "); sb.append(hdrCapabilities); + sb.append(", isForceSdr "); + sb.append(isForceSdr); sb.append(", userDisabledHdrTypes "); sb.append(Arrays.toString(userDisabledHdrTypes)); sb.append(", minimalPostProcessingSupported "); diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 6b4340a02edc..15a4715bd059 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -38,6 +38,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,7 +56,6 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import com.android.internal.annotations.VisibleForTesting; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.Objects; @@ -146,7 +146,7 @@ public class InsetsState implements Parcelable { forceConsumingTypes |= type; } - if (Flags.enableCaptionCompatInsetForceConsumptionAlways() + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled() && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) { forceConsumingOpaqueCaptionBar = true; } diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index dd950e83dd52..b21e85aeeb6a 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -174,24 +174,26 @@ public final class PointerIcon implements Parcelable { @IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_FILL_"}, value = { POINTER_ICON_VECTOR_STYLE_FILL_BLACK, POINTER_ICON_VECTOR_STYLE_FILL_GREEN, - POINTER_ICON_VECTOR_STYLE_FILL_YELLOW, + POINTER_ICON_VECTOR_STYLE_FILL_RED, POINTER_ICON_VECTOR_STYLE_FILL_PINK, - POINTER_ICON_VECTOR_STYLE_FILL_BLUE + POINTER_ICON_VECTOR_STYLE_FILL_BLUE, + POINTER_ICON_VECTOR_STYLE_FILL_PURPLE }) @Retention(RetentionPolicy.SOURCE) public @interface PointerIconVectorStyleFill {} /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLACK = 0; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_GREEN = 1; - /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_YELLOW = 2; + /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_RED = 2; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PINK = 3; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLUE = 4; + /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PURPLE = 5; // If adding a PointerIconVectorStyleFill, update END value for {@link SystemSettingsValidators} /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BEGIN = POINTER_ICON_VECTOR_STYLE_FILL_BLACK; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_END = - POINTER_ICON_VECTOR_STYLE_FILL_BLUE; + POINTER_ICON_VECTOR_STYLE_FILL_PURPLE; /** @hide */ @IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_STROKE_"}, value = { @@ -712,12 +714,14 @@ public final class PointerIcon implements Parcelable { com.android.internal.R.style.PointerIconVectorStyleFillBlack; case POINTER_ICON_VECTOR_STYLE_FILL_GREEN -> com.android.internal.R.style.PointerIconVectorStyleFillGreen; - case POINTER_ICON_VECTOR_STYLE_FILL_YELLOW -> - com.android.internal.R.style.PointerIconVectorStyleFillYellow; + case POINTER_ICON_VECTOR_STYLE_FILL_RED -> + com.android.internal.R.style.PointerIconVectorStyleFillRed; case POINTER_ICON_VECTOR_STYLE_FILL_PINK -> com.android.internal.R.style.PointerIconVectorStyleFillPink; case POINTER_ICON_VECTOR_STYLE_FILL_BLUE -> com.android.internal.R.style.PointerIconVectorStyleFillBlue; + case POINTER_ICON_VECTOR_STYLE_FILL_PURPLE -> + com.android.internal.R.style.PointerIconVectorStyleFillPurple; default -> com.android.internal.R.style.PointerIconVectorStyleFillBlack; }; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e10cc28d0745..33e79059c7e5 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -125,11 +125,11 @@ import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; -import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption; import static com.android.window.flags.Flags.insetsControlChangedItem; import static com.android.window.flags.Flags.insetsControlSeq; import static com.android.window.flags.Flags.setScPropertiesInClient; @@ -829,6 +829,7 @@ public final class ViewRootImpl implements ViewParent, private final SurfaceControl mSurfaceControl = new SurfaceControl(); private BLASTBufferQueue mBlastBufferQueue; + private IBinder mBbqApplyToken = new Binder(); private final HdrRenderState mHdrRenderState = new HdrRenderState(this); @@ -2743,6 +2744,10 @@ public final class ViewRootImpl implements ViewParent, mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); + // If we create and destroy BBQ without recreating the SurfaceControl, we can end up + // queuing buffers on multiple apply tokens causing out of order buffer submissions. We + // fix this by setting the same apply token on all BBQs created by this VRI. + mBlastBufferQueue.setApplyToken(mBbqApplyToken); Surface blastSurface; if (addSchandleToVriSurface()) { blastSurface = mBlastBufferQueue.createSurfaceWithHandle(); @@ -3209,10 +3214,10 @@ public final class ViewRootImpl implements ViewParent, typesToShow |= Type.navigationBars(); } if (captionIsHiddenByFlags && !captionWasHiddenByFlags - && enableCaptionCompatInsetForceConsumption()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { typesToHide |= Type.captionBar(); } else if (!captionIsHiddenByFlags && captionWasHiddenByFlags - && enableCaptionCompatInsetForceConsumption()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { typesToShow |= Type.captionBar(); } if (typesToHide != 0) { diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index b9751c8cab52..d90455ab971d 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -920,7 +920,8 @@ public interface ImeTracker { final Configuration.Builder builder = Configuration.Builder.withSurface( cujType, jankContext.getDisplayContext(), - jankContext.getTargetSurfaceControl()) + jankContext.getTargetSurfaceControl(), + jankContext.getDisplayContext().getMainThreadHandler()) .setTag(String.format(Locale.US, "%d@%d@%s", animType, useSeparatedThread ? 0 : 1, jankContext.getHostPackageName())); InteractionJankMonitor.getInstance().begin(builder); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e1154ca0701c..06820cd4c2ce 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1003,6 +1003,55 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_EMPTY_VIEW_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + out.write(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + appResources.getResourceName(mViewId)); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetEmptyViewAction.VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.VIEW_ID); + int emptyViewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID); + return new SetEmptyView(viewId, emptyViewId); + }; + } } private static class SetPendingIntentTemplate extends Action { @@ -1243,6 +1292,68 @@ public class RemoteViews implements Parcelable, Filter { mItems.visitUris(visitor); } + + @Override + public boolean canWriteToProto() { + // Skip actions that do not contain items (intent only actions) + return mItems != null; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + if (mItems == null) return; + final long token = out.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + out.write(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + appResources.getResourceName(mViewId)); + final long itemsToken = out.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + mItems.writeToProto(context, out, /* attached= */ true); + out.end(itemsToken); + out.end(token); + } + } + + private PendingResources<Action> createSetRemoteCollectionItemListAdapterActionFromProto( + ProtoInputStream in) throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID: + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + in.readString( + RemoteViewsProto + .SetRemoteCollectionItemListAdapterAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS: + final long itemsToken = in.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS, + RemoteCollectionItems.createFromProto(in)); + in.end(itemsToken); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID); + return new SetRemoteCollectionItemListAdapterAction(viewId, + ((PendingResources<RemoteCollectionItems>) values.get( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS)) + .create(context, resources, rootData, depth)); + }; } /** @@ -2036,6 +2147,68 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_DRAWABLE_TINT_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + out.write(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, mColorFilter); + out.write(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.modeToInt(mFilterMode)); + out.write(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, mTargetBackground); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetDrawableTintAction.VIEW_ID: + values.put(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + in.readString(RemoteViewsProto.SetDrawableTintAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND: + values.put(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, + in.readBoolean( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER: + values.put(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, + in.readInt(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.FILTER_MODE: + values.put(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.intToMode(in.readInt( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE))); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetDrawableTintAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetDrawableTintAction.VIEW_ID); + return new SetDrawableTint(viewId, (boolean) values.get( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, false), + (int) values.get(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 0), + (PorterDuff.Mode) values.get( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE)); + }; + } } /** @@ -2047,7 +2220,7 @@ public class RemoteViews implements Parcelable, Filter { * target {@link View#getBackground()}. * <p> */ - private class SetRippleDrawableColor extends Action { + private static class SetRippleDrawableColor extends Action { ColorStateList mColorStateList; SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) { @@ -2082,6 +2255,58 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RIPPLE_DRAWABLE_COLOR_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + out.write(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + appResources.getResourceName(mViewId)); + writeColorStateListToProto(out, mColorStateList, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST, + createColorStateListFromProto(in, + RemoteViewsProto + .SetRippleDrawableColorAction.COLOR_STATE_LIST)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID); + return new SetRippleDrawableColor(viewId, (ColorStateList) values.get( + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST)); + }; + } } /** @@ -2987,6 +3212,82 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return RESOURCE_REFLECTION_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + out.write(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, mMethodName); + out.write(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, mType); + out.write(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, mResourceType); + if (mResId != 0) { + out.write(RemoteViewsProto.ResourceReflectionAction.RES_ID, + appResources.getResourceName(mResId)); + } + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.ResourceReflectionAction.VIEW_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.METHOD_NAME: + values.put(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + in.readString( + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RES_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.RES_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.RES_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.VIEW_ID); + + int resId = (values.indexOfKey(RemoteViewsProto.ResourceReflectionAction.RES_ID) + >= 0) ? getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.RES_ID) : 0; + return new ResourceReflectionAction(viewId, + (String) values.get(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + 0), resId); + }; + } } private static final class AttributeReflectionAction extends BaseReflectionAction { @@ -4593,6 +4894,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_INT_TAG_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + out.write(RemoteViewsProto.SetIntTagAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetIntTagAction.KEY, + appResources.getResourceName(mKey)); // rebase + out.write(RemoteViewsProto.SetIntTagAction.TAG, mTag); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetIntTagAction.VIEW_ID: + values.put(RemoteViewsProto.SetIntTagAction.VIEW_ID, + in.readString(RemoteViewsProto.SetIntTagAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetIntTagAction.KEY: + values.put(RemoteViewsProto.SetIntTagAction.KEY, + in.readString(RemoteViewsProto.SetIntTagAction.KEY)); + break; + case (int) RemoteViewsProto.SetIntTagAction.TAG: + values.put(RemoteViewsProto.SetIntTagAction.TAG, + in.readInt(RemoteViewsProto.SetIntTagAction.TAG)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetIntTagAction.VIEW_ID, + RemoteViewsProto.SetIntTagAction.KEY}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.VIEW_ID); + int keyId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.KEY); + return new SetIntTagAction(viewId, keyId, + (int) values.get(RemoteViewsProto.SetIntTagAction.TAG, 0)); + }; + } } private static class SetCompoundButtonCheckedAction extends Action { @@ -4643,6 +4999,56 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_COMPOUND_BUTTON_CHECKED_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start( + RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, mChecked); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, + in.readBoolean( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID); + return new SetCompoundButtonCheckedAction(viewId, (boolean) values.get( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, false)); + }; + } } private static class SetRadioGroupCheckedAction extends Action { @@ -4707,6 +5113,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RADIO_GROUP_CHECKED; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + if (mCheckedId != -1) { + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + appResources.getResourceName(mCheckedId)); + } + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + in.readString(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + in.readString( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID); + + int checkedId = (values.indexOfKey( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) >= 0) + ? getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) : -1; + return new SetRadioGroupCheckedAction(viewId, checkedId); + }; + } } private static class SetViewOutlinePreferredRadiusAction extends Action { @@ -8450,6 +8911,7 @@ public class RemoteViews implements Parcelable, Filter { public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in) throws Exception { final LongSparseArray<Object> values = new LongSparseArray<>(); + values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>()); values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS, new ArrayList<PendingResources<RemoteViews>>()); @@ -9207,6 +9669,22 @@ public class RemoteViews implements Parcelable, Filter { return ReflectionAction.createFromProto(in); case (int) RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION: return RemoveFromParentAction.createFromProto(in); + case (int) RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION: + return ResourceReflectionAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION: + return SetCompoundButtonCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION: + return SetDrawableTint.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION: + return SetEmptyView.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_INT_TAG_ACTION: + return SetIntTagAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION: + return SetRadioGroupCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION: + return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in); + case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION: + return SetRippleDrawableColor.createFromProto(in); default: throw new RuntimeException("Unhandled field while reading Action proto!\n" + ProtoUtils.currentFieldToString(in)); diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java index 5c53d66e49fe..701b6be06e72 100644 --- a/core/java/android/window/flags/DesktopModeFlags.java +++ b/core/java/android/window/flags/DesktopModeFlags.java @@ -17,7 +17,9 @@ package android.window.flags; import android.annotation.Nullable; -import android.content.Context; +import android.app.ActivityThread; +import android.app.Application; +import android.content.ContentResolver; import android.provider.Settings; import android.util.Log; @@ -39,9 +41,13 @@ import java.util.function.Supplier; */ public enum DesktopModeFlags { // All desktop mode related flags to be overridden by developer option toggle will be added here - DESKTOP_WINDOWING_MODE( + ENABLE_DESKTOP_WINDOWING_MODE( Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true), - DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false); + ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false), + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION( + Flags::enableCaptionCompatInsetForceConsumption, true), + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS( + Flags::enableCaptionCompatInsetForceConsumptionAlways, true); private static final String TAG = "DesktopModeFlagsUtil"; // Function called to obtain aconfig flag value. @@ -62,14 +68,15 @@ public enum DesktopModeFlags { * Determines state of flag based on the actual flag and desktop mode developer option * overrides. */ - public boolean isEnabled(Context context) { + public boolean isEnabled() { + Application application = ActivityThread.currentApplication(); if (!Flags.showDesktopWindowingDevOption() || !mShouldOverrideByDevOption - || context.getContentResolver() == null) { + || application == null) { return mFlagFunction.get(); } else { boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode(); - return switch (getToggleOverride(context)) { + return switch (getToggleOverride(application.getContentResolver())) { case OVERRIDE_UNSET -> mFlagFunction.get(); // When toggle override matches its default state, don't override flags. This // helps users reset their feature overrides. @@ -79,14 +86,14 @@ public enum DesktopModeFlags { } } - private ToggleOverride getToggleOverride(Context context) { + private ToggleOverride getToggleOverride(ContentResolver contentResolver) { // If cached, return it if (sCachedToggleOverride != null) { return sCachedToggleOverride; } // Otherwise, fetch and cache it - ToggleOverride override = getToggleOverrideFromSystem(context); + ToggleOverride override = getToggleOverrideFromSystem(contentResolver); sCachedToggleOverride = override; Log.d(TAG, "Toggle override initialized to: " + override); return override; @@ -95,9 +102,9 @@ public enum DesktopModeFlags { /** * Returns {@link ToggleOverride} from Settings.Global set by toggle. */ - private ToggleOverride getToggleOverrideFromSystem(Context context) { + private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) { int settingValue = Settings.Global.getInt( - context.getContentResolver(), + contentResolver, Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, ToggleOverride.OVERRIDE_UNSET.getSetting() ); diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index c7e1fba66d7f..ef08e49ce6d9 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -330,9 +330,10 @@ public class InteractionJankMonitor { * @param cujType the specific {@link Cuj.CujType}. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType) { + public boolean begin(SurfaceControl surface, Context context, Handler handler, + @Cuj.CujType int cujType) { try { - return begin(Configuration.Builder.withSurface(cujType, context, surface)); + return begin(Configuration.Builder.withSurface(cujType, context, surface, handler)); } catch (IllegalArgumentException ex) { Log.d(TAG, "Build configuration failed!", ex); return false; @@ -348,11 +349,12 @@ public class InteractionJankMonitor { * @param tag a tag containing extra information about the interaction. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType, + public boolean begin(SurfaceControl surface, Context context, Handler handler, + @Cuj.CujType int cujType, String tag) { try { final Configuration.Builder builder = - Configuration.Builder.withSurface(cujType, context, surface); + Configuration.Builder.withSurface(cujType, context, surface, handler); if (!TextUtils.isEmpty(tag)) { builder.setTag(tag); } @@ -689,20 +691,23 @@ public class InteractionJankMonitor { private SurfaceControl mAttrSurfaceControl; private final @Cuj.CujType int mAttrCujType; private boolean mAttrDeferMonitor = true; + private Handler mHandler = null; /** * Creates a builder which instruments only surface. * @param cuj The enum defined in {@link Cuj.CujType}. * @param context context * @param surfaceControl surface control + * @param uiThreadHandler UI thread for that surface * @return builder */ public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context, - @NonNull SurfaceControl surfaceControl) { + @NonNull SurfaceControl surfaceControl, @NonNull Handler uiThreadHandler) { return new Builder(cuj) .setContext(context) .setSurfaceControl(surfaceControl) - .setSurfaceOnly(true); + .setSurfaceOnly(true) + .setHandler(uiThreadHandler); } /** @@ -722,6 +727,18 @@ public class InteractionJankMonitor { } /** + * Specifies the UI thread handler. If not provided, the View's one will be used. + * If only a surface is provided without handler, the app main thread will be used. + * + * @param uiThreadHandler handler associated to the cuj UI thread + * @return builder + */ + public Builder setHandler(Handler uiThreadHandler) { + mHandler = uiThreadHandler; + return this; + } + + /** * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false. * @param view an attached view * @return builder @@ -798,13 +815,13 @@ public class InteractionJankMonitor { return new Configuration( mAttrCujType, mAttrView, mAttrTag, mAttrTimeout, mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl, - mAttrDeferMonitor); + mAttrDeferMonitor, mHandler); } } private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout, boolean surfaceOnly, Context context, SurfaceControl surfaceControl, - boolean deferMonitor) { + boolean deferMonitor, Handler handler) { mCujType = cuj; mTag = tag; mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag); @@ -816,8 +833,16 @@ public class InteractionJankMonitor { : (view != null ? view.getContext().getApplicationContext() : null); mSurfaceControl = surfaceControl; mDeferMonitor = deferMonitor; + if (handler != null) { + mHandler = handler; + } else if (mSurfaceOnly) { + Log.w(TAG, "No UIThread provided for " + mSessionName + + " (surface only). Defaulting to app main thread."); + mHandler = mContext.getMainThreadHandler(); + } else { + mHandler = mView.getHandler(); + } validate(); - mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler(); } @VisibleForTesting @@ -858,6 +883,12 @@ public class InteractionJankMonitor { shouldThrow = true; msg.append("Must pass in a valid surface control if only instrument surface; "); } + if (mHandler == null) { + shouldThrow = true; + msg.append( + "Must pass a UI thread handler when only a surface control is " + + "provided."); + } } else { if (!hasValidView()) { shouldThrow = true; diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 4708be8108c2..84dfc497dc84 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -38,6 +38,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATIO import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.flags.Flags.customizableWindowHeaders; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL; @@ -114,7 +116,6 @@ import com.android.internal.view.menu.MenuHelper; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; -import com.android.window.flags.Flags; import java.util.List; import java.util.concurrent.Executor; @@ -1217,14 +1218,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final boolean hideCaptionBar = fullscreen || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0; - final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption() - && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0 + final boolean consumingCaptionBar = + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled() + && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0 && hideCaptionBar); final boolean isOpaqueCaptionBar = customizableWindowHeaders() && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0; final boolean consumingOpaqueCaptionBar = - Flags.enableCaptionCompatInsetForceConsumptionAlways() + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled() && mLastForceConsumingOpaqueCaptionBar && isOpaqueCaptionBar; diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 90cb10aa62b2..9a4ff8fc264f 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -290,7 +290,6 @@ cc_library_shared_for_libandroid_runtime { "libasync_safe", "libbinderthreadstateutils", "libdmabufinfo", - "libgif", "libgui_window_info_static", "libkernelconfigs", "libnativehelper_lazy", diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 70505a45fa1b..b9c3bf73f11c 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -16,16 +16,16 @@ #define LOG_TAG "BLASTBufferQueue" -#include <nativehelper/JNIHelp.h> - #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_view_Surface.h> -#include <utils/Log.h> -#include <utils/RefBase.h> - +#include <android_util_Binder.h> #include <gui/BLASTBufferQueue.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> +#include <nativehelper/JNIHelp.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + #include "core_jni_helpers.h" namespace android { @@ -209,6 +209,12 @@ static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong reinterpret_cast<jlong>(transaction)); } +static void nativeSetApplyToken(JNIEnv* env, jclass clazz, jlong ptr, jobject applyTokenObject) { + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); + sp<IBinder> token(ibinderForJavaObject(env, applyTokenObject)); + return queue->setApplyToken(std::move(token)); +} + static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off @@ -227,6 +233,7 @@ static const JNINativeMethod gMethods[] = { {"nativeSetTransactionHangCallback", "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V", (void*)nativeSetTransactionHangCallback}, + {"nativeSetApplyToken", "(JLandroid/os/IBinder;)V", (void*)nativeSetApplyToken}, // clang-format on }; diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index f2c70b5f41d4..8003bb7d442b 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1747,9 +1747,9 @@ static const JNINativeMethod gBinderProxyMethods[] = { {"linkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, {"addFrozenStateChangeCallbackNative", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback}, + "(Landroid/os/IBinder$FrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback}, {"removeFrozenStateChangeCallbackNative", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback}, + "(Landroid/os/IBinder$FrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback}, {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, {"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension}, }; @@ -1774,7 +1774,7 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V"); gBinderProxyOffsets.mInvokeFrozenStateChangeCallback = GetStaticMethodIDOrDie(env, clazz, "invokeFrozenStateChangeCallback", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;Landroid/os/" + "(Landroid/os/IBinder$FrozenStateChangeCallback;Landroid/os/" "IBinder;I)V"); gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h index 3b29e305e410..21b5b1308fcf 100644 --- a/core/jni/jni_wrappers.h +++ b/core/jni/jni_wrappers.h @@ -69,9 +69,47 @@ static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { return static_cast<T>(res); } +// Inline variable that specifies the method binding format. +// The expected format is 'XX${method}XX', where ${method} represents the original method name. +// This variable is shared across all translation units. This is treated as a global variable as +// per C++ 17. +inline std::string jniMethodFormat; + +inline static void setJniMethodFormat(std::string value) { + jniMethodFormat = value; +} + +// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set. +// Has no effect otherwise +inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods, + int numMethods) { + if (jniMethodFormat.empty()) { + return gMethods; + } + // Make a copy of gMethods with reformatted method names. + JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods]; + LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods"); + + size_t methodNamePos = jniMethodFormat.find("${method}"); + LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos, + "Invalid jniMethodFormat: could not find '${method}' in pattern"); + + for (int i = 0; i < numMethods; i++) { + modifiedMethods[i] = gMethods[i]; + std::string modifiedName = jniMethodFormat; + modifiedName.replace(methodNamePos, 9, gMethods[i].name); + char* modifiedNameChars = new char[modifiedName.length() + 1]; + LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name"); + std::strcpy(modifiedNameChars, modifiedName.c_str()); + modifiedMethods[i].name = modifiedNameChars; + } + return modifiedMethods; +} + static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - int res = jniRegisterNativeMethods(env, className, gMethods, numMethods); + const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods); + int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); return res; } diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto index 47c97b08666b..f477d32cd915 100644 --- a/core/proto/android/widget/remoteviews.proto +++ b/core/proto/android/widget/remoteviews.proto @@ -299,6 +299,14 @@ message RemoteViewsProto { NightModeReflectionAction night_mode_reflection_action = 5; ReflectionAction reflection_action = 6; RemoveFromParentAction remove_from_parent_action = 7; + ResourceReflectionAction resource_reflection_action = 8; + SetCompoundButtonCheckedAction set_compound_button_checked_action = 9; + SetDrawableTintAction set_drawable_tint_action = 10; + SetEmptyViewAction set_empty_view_action = 11; + SetIntTagAction set_int_tag_action = 12; + SetRadioGroupCheckedAction set_radio_group_checked_action = 13; + SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14; + SetRippleDrawableColorAction set_ripple_drawable_color_action = 15; } } @@ -374,6 +382,52 @@ message RemoteViewsProto { message RemoveFromParentAction { optional string view_id = 1; } + + message ResourceReflectionAction { + optional string view_id = 1; + optional string method_name = 2; + optional int32 resource_type = 3; + optional string res_id = 4; + optional int32 parameter_type = 5; + } + + message SetCompoundButtonCheckedAction { + optional string view_id = 1; + optional bool checked = 2; + } + + message SetDrawableTintAction { + optional string view_id = 1; + optional bool target_background = 2; + optional int32 color_filter = 3; + optional int32 filter_mode = 4; + } + + message SetEmptyViewAction { + optional string view_id = 1; + optional string empty_view_id = 2; + } + + message SetIntTagAction { + optional string view_id = 1; + optional string key = 2; + optional int32 tag = 3; + } + + message SetRadioGroupCheckedAction { + optional string view_id = 1; + optional string checked_id = 2; + } + + message SetRemoteCollectionItemListAdapterAction { + optional string view_id = 1; + optional RemoteCollectionItems items = 2; + } + + message SetRippleDrawableColorAction { + optional string view_id = 1; + optional android.content.res.ColorStateListProto color_state_list = 2; + } } diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index e60e0b0079a9..db1c779d0087 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -122,7 +122,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" - android:maxLines="2" + android:layout_toStartOf="@id/button_open" + android:layout_marginEnd="8dp" android:background="@drawable/resolver_outlined_button_bg" style="?android:attr/borderlessButtonStyle" android:paddingHorizontal="16dp" @@ -136,7 +137,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:maxLines="2" android:paddingHorizontal="16dp" android:background="@drawable/resolver_button_bg" style="?android:attr/borderlessButtonStyle" diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index dc99634ddabc..579dc91d2ca1 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1509,7 +1509,7 @@ please see styles_device_defaults.xml. </style> <!-- @hide --> - <style name="PointerIconVectorStyleFillYellow"> + <style name="PointerIconVectorStyleFillRed"> <item name="pointerIconVectorFill">#F55E57</item> <item name="pointerIconVectorFillInverse">#F55E57</item> </style> @@ -1527,6 +1527,12 @@ please see styles_device_defaults.xml. </style> <!-- @hide --> + <style name="PointerIconVectorStyleFillPurple"> + <item name="pointerIconVectorFill">#AD72FF</item> + <item name="pointerIconVectorFillInverse">#AD72FF</item> + </style> + + <!-- @hide --> <style name="PointerIconVectorStyleStrokeWhite"> <item name="pointerIconVectorStroke">@color/white</item> <item name="pointerIconVectorStrokeInverse">@color/black</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1917ecdd99b4..039665982482 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1705,9 +1705,10 @@ <java-symbol type="style" name="VectorPointer" /> <java-symbol type="style" name="PointerIconVectorStyleFillBlack" /> <java-symbol type="style" name="PointerIconVectorStyleFillGreen" /> - <java-symbol type="style" name="PointerIconVectorStyleFillYellow" /> + <java-symbol type="style" name="PointerIconVectorStyleFillRed" /> <java-symbol type="style" name="PointerIconVectorStyleFillPink" /> <java-symbol type="style" name="PointerIconVectorStyleFillBlue" /> + <java-symbol type="style" name="PointerIconVectorStyleFillPurple" /> <java-symbol type="attr" name="pointerIconVectorFill" /> <java-symbol type="style" name="PointerIconVectorStyleStrokeWhite" /> <java-symbol type="style" name="PointerIconVectorStyleStrokeBlack" /> diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java index 77e8a404a0ff..fe54aa8d87f0 100644 --- a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java +++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java @@ -30,31 +30,30 @@ import java.util.concurrent.TimeUnit; public class BfsccTestAppCmdService extends Service { private IBfsccTestAppCmdService.Stub mBinder = new IBfsccTestAppCmdService.Stub() { - private final LinkedBlockingQueue<IBinder.IFrozenStateChangeCallback.State> mNotifications = + private final LinkedBlockingQueue<Integer> mNotifications = new LinkedBlockingQueue<>(); @Override public void listenTo(IBinder binder) throws RemoteException { binder.addFrozenStateChangeCallback( - (IBinder who, IBinder.IFrozenStateChangeCallback.State state) - -> mNotifications.offer(state)); + (IBinder who, int state) -> mNotifications.offer(state)); } @Override public boolean[] waitAndConsumeNotifications() { List<Boolean> results = new ArrayList<>(); try { - IBinder.IFrozenStateChangeCallback.State state = - mNotifications.poll(5, TimeUnit.SECONDS); + Integer state = mNotifications.poll(5, TimeUnit.SECONDS); if (state != null) { - results.add(state == IBinder.IFrozenStateChangeCallback.State.FROZEN); + results.add( + state.intValue() == IBinder.FrozenStateChangeCallback.STATE_FROZEN); } } catch (InterruptedException e) { return null; } while (mNotifications.size() > 0) { - results.add(mNotifications.poll() - == IBinder.IFrozenStateChangeCallback.State.FROZEN); + results.add(mNotifications.poll().intValue() + == IBinder.FrozenStateChangeCallback.STATE_FROZEN); } boolean[] convertedResults = new boolean[results.size()]; for (int i = 0; i < results.size(); i++) { diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java index ee2e7e06081e..195a18a5f521 100644 --- a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java +++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java @@ -52,7 +52,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** - * Tests functionality of {@link android.os.IBinder.IFrozenStateChangeCallback}. + * Tests functionality of {@link android.os.IBinder.FrozenStateChangeCallback}. */ @RunWith(AndroidJUnit4.class) @IgnoreUnderRavenwood(blockedBy = ActivityManager.class) @@ -157,7 +157,7 @@ public class BinderFrozenStateChangeNotificationTest { @Test public void onStateChangeNotCalledAfterCallbackRemoved() throws Exception { final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>(); - IBinder.IFrozenStateChangeCallback callback; + IBinder.FrozenStateChangeCallback callback; if ((callback = createCallback(mBfsccTestAppCmdService.asBinder(), results)) == null) { return; } @@ -171,7 +171,7 @@ public class BinderFrozenStateChangeNotificationTest { public void multipleCallbacks() throws Exception { final LinkedBlockingQueue<Boolean> results1 = new LinkedBlockingQueue<>(); final LinkedBlockingQueue<Boolean> results2 = new LinkedBlockingQueue<>(); - IBinder.IFrozenStateChangeCallback callback1; + IBinder.FrozenStateChangeCallback callback1; if ((callback1 = createCallback(mBfsccTestAppCmdService.asBinder(), results1)) == null) { return; } @@ -197,8 +197,8 @@ public class BinderFrozenStateChangeNotificationTest { public void onStateChangeCalledWithTheRightBinder() throws Exception { final IBinder binder = mBfsccTestAppCmdService.asBinder(); final LinkedBlockingQueue<IBinder> results = new LinkedBlockingQueue<>(); - IBinder.IFrozenStateChangeCallback callback = - (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> results.offer(who); + IBinder.FrozenStateChangeCallback callback = + (IBinder who, int state) -> results.offer(who); try { binder.addFrozenStateChangeCallback(callback); } catch (UnsupportedOperationException e) { @@ -221,12 +221,12 @@ public class BinderFrozenStateChangeNotificationTest { } } - private IBinder.IFrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue) + private IBinder.FrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue) throws RemoteException { try { - final IBinder.IFrozenStateChangeCallback callback = - (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> - queue.offer(state == IBinder.IFrozenStateChangeCallback.State.FROZEN); + final IBinder.FrozenStateChangeCallback callback = + (IBinder who, int state) -> + queue.offer(state == IBinder.FrozenStateChangeCallback.STATE_FROZEN); binder.addFrozenStateChangeCallback(callback); return callback; } catch (UnsupportedOperationException e) { diff --git a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java index 32345e606229..dd406955785b 100644 --- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java +++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java @@ -16,7 +16,7 @@ package android.window.flags; -import static android.window.flags.DesktopModeFlags.DESKTOP_WINDOWING_MODE; +import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE; import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS; @@ -75,7 +75,7 @@ public class DesktopModeFlagsTest { public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_OFF_SETTING); // In absence of dev options, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @@ -84,7 +84,7 @@ public class DesktopModeFlagsTest { public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_ON_SETTING); - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -93,7 +93,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -103,7 +103,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -112,7 +112,7 @@ public class DesktopModeFlagsTest { setOverride(null); // For overridableFlag, in absence of overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -122,7 +122,7 @@ public class DesktopModeFlagsTest { setOverride(null); // For overridableFlag, in absence of overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -131,7 +131,7 @@ public class DesktopModeFlagsTest { setOverride(-2); // For overridableFlag, for unrecognized overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -141,7 +141,7 @@ public class DesktopModeFlagsTest { setOverride(-2); // For overridableFlag, for unrecognizable overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -150,7 +150,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -160,7 +160,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -169,12 +169,12 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); setOverride(OVERRIDE_ON_SETTING); // Keep overrides constant through the process - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -184,12 +184,12 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); setOverride(OVERRIDE_OFF_SETTING); // Keep overrides constant through the process - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -199,7 +199,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -208,7 +208,7 @@ public class DesktopModeFlagsTest { public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -221,7 +221,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -231,7 +231,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -244,7 +244,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -254,7 +254,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -267,7 +267,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -280,7 +280,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -293,7 +293,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -306,7 +306,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -319,7 +319,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -332,7 +332,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } private void setOverride(Integer setting) { diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index 5af272c8bead..d6b2a782bc0c 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -117,6 +117,7 @@ public class InteractionJankMonitorTest { // Simulate a trace session and see if begin / end are invoked. assertThat(monitor.begin(mSurfaceControl, mActivity.getApplicationContext(), + mActivity.getMainThreadHandler(), Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).begin(); assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java index 397cdcf6acdd..67de25eede42 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java @@ -125,12 +125,12 @@ public class BinderDeathDispatcherTest { } @Override - public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback) + public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback) throws RemoteException { } @Override - public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) { + public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) { return false; } diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index c52f700ef4f6..90723b2f1493 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -17,6 +17,7 @@ package android.graphics; import android.annotation.NonNull; +import android.os.IBinder; import android.view.Surface; import android.view.SurfaceControl; @@ -47,6 +48,7 @@ public final class BLASTBufferQueue { long frameNumber); private static native void nativeSetTransactionHangCallback(long ptr, TransactionHangCallback callback); + private static native void nativeSetApplyToken(long ptr, IBinder applyToken); public interface TransactionHangCallback { void onTransactionHang(String reason); @@ -204,4 +206,8 @@ public final class BLASTBufferQueue { public void setTransactionHangCallback(TransactionHangCallback hangCallback) { nativeSetTransactionHangCallback(mNativeObject, hangCallback); } + + public void setApplyToken(IBinder applyToken) { + nativeSetApplyToken(mNativeObject, applyToken); + } } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index 9027bf34a58e..88878c6adcf2 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -40,6 +40,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; +import android.app.TaskInfo; import android.app.WindowConfiguration; import android.graphics.Rect; import android.util.ArrayMap; @@ -339,6 +340,52 @@ public class TransitionUtil { return target; } + /** + * Creates a new RemoteAnimationTarget from the provided change and leash + */ + public static RemoteAnimationTarget newSyntheticTarget(ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl leash, @TransitionInfo.TransitionMode int mode, int order, + boolean isTranslucent) { + int taskId; + boolean isNotInRecents; + WindowConfiguration windowConfiguration; + + if (taskInfo != null) { + taskId = taskInfo.taskId; + isNotInRecents = !taskInfo.isRunning; + windowConfiguration = taskInfo.configuration.windowConfiguration; + } else { + taskId = INVALID_TASK_ID; + isNotInRecents = true; + windowConfiguration = new WindowConfiguration(); + } + + Rect localBounds = new Rect(); + RemoteAnimationTarget target = new RemoteAnimationTarget( + taskId, + newModeToLegacyMode(mode), + // TODO: once we can properly sync transactions across process, + // then get rid of this leash. + leash, + isTranslucent, + null, + // TODO(shell-transitions): we need to send content insets? evaluate how its used. + new Rect(0, 0, 0, 0), + order, + null, + localBounds, + new Rect(), + windowConfiguration, + isNotInRecents, + null, + new Rect(), + taskInfo, + false, + INVALID_WINDOW_TYPE + ); + return target; + } + private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change, SurfaceControl leash) { return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 452d12a242c0..7e6f43458ba6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -46,7 +46,6 @@ import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl; import android.window.ITaskOrganizerController; -import android.window.ScreenCapture; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; @@ -55,7 +54,6 @@ import android.window.TaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; -import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.compatui.api.CompatUIHandler; @@ -74,7 +72,6 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Consumer; /** * Unified task organizer for all components in the shell. @@ -561,19 +558,6 @@ public class ShellTaskOrganizer extends TaskOrganizer { mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo())); } - /** - * Take a screenshot of a task. - */ - public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, - Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) { - final TaskAppearedInfo info = mTasks.get(taskInfo.taskId); - if (info == null) { - return; - } - ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer); - } - - @Override public void onTaskInfoChanged(RunningTaskInfo taskInfo) { synchronized (mLock) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index f478b4446cbe..3e5adf395cdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -117,6 +117,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ private static final long MAX_ANIMATION_DURATION = 2000; private final LatencyTracker mLatencyTracker; + @ShellMainThread private final Handler mHandler; /** True when a back gesture is ongoing */ private boolean mBackGestureStarted = false; @@ -218,7 +219,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, - Transitions transitions) { + Transitions transitions, + @ShellMainThread Handler handler) { this( shellInit, shellController, @@ -230,7 +232,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont backAnimationBackground, shellBackAnimationRegistry, shellCommandHandler, - transitions); + transitions, + handler); } @VisibleForTesting @@ -245,7 +248,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, - Transitions transitions) { + Transitions transitions, + @NonNull @ShellMainThread Handler handler) { mShellController = shellController; mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; @@ -263,6 +267,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransitions = transitions; mBackTransitionHandler = new BackTransitionHandler(); mTransitions.addHandler(mBackTransitionHandler); + mHandler = handler; updateTouchableArea(); } @@ -399,7 +404,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - private static class IBackAnimationImpl extends IBackAnimation.Stub + private class IBackAnimationImpl extends IBackAnimation.Stub implements ExternalInterfaceBinder { private BackAnimationController mController; @@ -417,7 +422,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont callback, runner, controller.mContext, - CUJ_PREDICTIVE_BACK_HOME))); + CUJ_PREDICTIVE_BACK_HOME, + mHandler))); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java index e24df0bdc05d..9ca9b730fb06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_OLD_UNSET; import android.annotation.NonNull; import android.content.Context; +import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.IRemoteAnimationFinishedCallback; @@ -31,6 +32,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; +import com.android.wm.shell.shared.annotations.ShellMainThread; /** * Used to register the animation callback and runner, it will trigger result if gesture was finish @@ -45,6 +47,8 @@ public class BackAnimationRunner { private final IRemoteAnimationRunner mRunner; private final @CujType int mCujType; private final Context mContext; + @ShellMainThread + private final Handler mHandler; // Whether we are waiting to receive onAnimationStart private boolean mWaitingAnimation; @@ -56,18 +60,35 @@ public class BackAnimationRunner { @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner, @NonNull Context context, - @CujType int cujType) { + @CujType int cujType, + @ShellMainThread Handler handler) { mCallback = callback; mRunner = runner; mCujType = cujType; mContext = context; + mHandler = handler; } public BackAnimationRunner( @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner, - @NonNull Context context) { - this(callback, runner, context, NO_CUJ); + @NonNull Context context, + @ShellMainThread Handler handler + ) { + this(callback, runner, context, NO_CUJ, handler); + } + + /** + * @deprecated Use {@link BackAnimationRunner} constructor providing an handler for the ui + * thread of the animation. + */ + @Deprecated + public BackAnimationRunner( + @NonNull IOnBackInvokedCallback callback, + @NonNull IRemoteAnimationRunner runner, + @NonNull Context context + ) { + this(callback, runner, context, NO_CUJ, context.getMainThreadHandler()); } /** Returns the registered animation runner */ @@ -100,7 +121,7 @@ public class BackAnimationRunner { mWaitingAnimation = false; if (shouldMonitorCUJ(apps)) { interactionJankMonitor.begin( - apps[0].leash, mContext, mCujType); + apps[0].leash, mContext, mHandler, mCujType); } try { getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 32e809a23a91..37339307f5b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -26,6 +26,7 @@ import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF +import android.os.Handler import android.os.RemoteException import android.util.TimeUtils import android.view.Choreographer @@ -53,6 +54,7 @@ import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.animation.Interpolators +import com.android.wm.shell.shared.annotations.ShellMainThread import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -61,7 +63,8 @@ abstract class CrossActivityBackAnimation( private val context: Context, private val background: BackAnimationBackground, private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, - protected val transaction: SurfaceControl.Transaction + protected val transaction: SurfaceControl.Transaction, + @ShellMainThread handler: Handler, ) : ShellBackAnimation() { protected val startClosingRect = RectF() @@ -80,7 +83,13 @@ abstract class CrossActivityBackAnimation( private var statusbarHeight = SystemBarUtils.getStatusBarHeight(context) private val backAnimationRunner = - BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY) + BackAnimationRunner( + Callback(), + Runner(), + context, + Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY, + handler, + ) private val initialTouchPos = PointF() private val transformMatrix = Matrix() private val tmpFloat9 = FloatArray(9) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 3fccecab4319..7a569799ab84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -34,6 +34,7 @@ import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; +import android.os.Handler; import android.os.RemoteException; import android.view.Choreographer; import android.view.IRemoteAnimationFinishedCallback; @@ -52,6 +53,7 @@ import com.android.internal.policy.SystemBarUtils; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.shared.animation.Interpolators; +import com.android.wm.shell.shared.annotations.ShellMainThread; import javax.inject.Inject; @@ -113,9 +115,10 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { private float mVerticalMargin; @Inject - public CrossTaskBackAnimation(Context context, BackAnimationBackground background) { + public CrossTaskBackAnimation(Context context, BackAnimationBackground background, + @ShellMainThread Handler handler) { mBackAnimationRunner = new BackAnimationRunner( - new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK); + new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK, handler); mBackground = background; mContext = context; loadResources(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt index b02f97bf7784..2f7666b21882 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.back import android.content.Context import android.graphics.Rect import android.graphics.RectF +import android.os.Handler import android.util.MathUtils import android.view.SurfaceControl import android.view.animation.Animation @@ -30,6 +31,7 @@ import com.android.internal.policy.TransitionAnimation import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.annotations.ShellMainThread import javax.inject.Inject import kotlin.math.max import kotlin.math.min @@ -40,13 +42,15 @@ class CustomCrossActivityBackAnimation( background: BackAnimationBackground, rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, transaction: SurfaceControl.Transaction, - private val customAnimationLoader: CustomAnimationLoader + private val customAnimationLoader: CustomAnimationLoader, + @ShellMainThread handler: Handler, ) : CrossActivityBackAnimation( context, background, rootTaskDisplayAreaOrganizer, - transaction + transaction, + handler ) { private var enterAnimation: Animation? = null @@ -59,7 +63,8 @@ class CustomCrossActivityBackAnimation( constructor( context: Context, background: BackAnimationBackground, - rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + @ShellMainThread handler: Handler, ) : this( context, background, @@ -67,7 +72,8 @@ class CustomCrossActivityBackAnimation( SurfaceControl.Transaction(), CustomAnimationLoader( TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation") - ) + ), + handler, ) override fun preparePreCommitClosingRectMovement(swipeEdge: Int) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt index 66d8a5f2eeb9..eecd7694009d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt @@ -16,11 +16,13 @@ package com.android.wm.shell.back import android.content.Context +import android.os.Handler import android.view.SurfaceControl import android.window.BackEvent import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.shared.animation.Interpolators +import com.android.wm.shell.shared.annotations.ShellMainThread import javax.inject.Inject import kotlin.math.max @@ -30,13 +32,15 @@ class DefaultCrossActivityBackAnimation constructor( context: Context, background: BackAnimationBackground, - rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + @ShellMainThread handler: Handler, ) : CrossActivityBackAnimation( context, background, rootTaskDisplayAreaOrganizer, - SurfaceControl.Transaction() + SurfaceControl.Transaction(), + handler ) { private val postCommitInterpolator = Interpolators.EMPHASIZED diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b8aa1b189f7e..4b55fd0f5527 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -48,6 +48,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; +import android.os.Handler; import android.view.Display; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -75,6 +76,7 @@ import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.Interpolators; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; @@ -116,6 +118,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange new PathInterpolator(0.2f, 0f, 0f, 1f); private static final Interpolator GROW_INTERPOLATOR = new PathInterpolator(0.45f, 0f, 0.5f, 1f); + @ShellMainThread + private final Handler mHandler; private int mDividerWindowWidth; private int mDividerInsets; @@ -166,7 +170,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, DisplayController displayController, DisplayImeController displayImeController, - ShellTaskOrganizer taskOrganizer, int parallaxType) { + ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) { + mHandler = handler; mContext = context.createConfigurationContext(configuration); mOrientation = configuration.orientation; mRotation = configuration.windowConfiguration.getRotation(); @@ -598,7 +603,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } void onStartDragging() { - mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE); + mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler, + CUJ_SPLIT_SCREEN_RESIZE); } void onDraggingCancelled() { @@ -756,7 +762,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange @Override public void onAnimationStart(Animator animation) { mInteractionJankMonitor.begin(getDividerLeash(), - mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); + mContext, mHandler, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 4adea233b734..722fe1f59388 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -444,7 +444,9 @@ public abstract class WMShellBaseModule { BackAnimationBackground backAnimationBackground, Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, - Transitions transitions) { + Transitions transitions, + @ShellMainThread Handler handler + ) { if (BackAnimationController.IS_ENABLED) { return shellBackAnimationRegistry.map( (animations) -> @@ -457,7 +459,8 @@ public abstract class WMShellBaseModule { backAnimationBackground, animations, shellCommandHandler, - transitions)); + transitions, + handler)); } return Optional.empty(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index b151c8b7e718..b47adb43c2a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -355,7 +355,8 @@ public abstract class WMShellModule { @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { return new FreeformTaskTransitionHandler( shellInit, transitions, @@ -365,7 +366,8 @@ public abstract class WMShellModule { mainExecutor, animExecutor, desktopModeTaskRepository, - interactionJankMonitor); + interactionJankMonitor, + handler); } @WMSingleton @@ -487,10 +489,11 @@ public abstract class WMShellModule { @Provides static RecentsTransitionHandler provideRecentsTransitionHandler( ShellInit shellInit, + ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, Optional<RecentTasksController> recentTasksController, HomeTransitionObserver homeTransitionObserver) { - return new RecentsTransitionHandler(shellInit, transitions, + return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions, recentTasksController.orElse(null), homeTransitionObserver); } @@ -616,6 +619,7 @@ public abstract class WMShellModule { RecentsTransitionHandler recentsTransitionHandler, MultiInstanceHelper multiInstanceHelper, @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<RecentTasksController> recentTasksController, InteractionJankMonitor interactionJankMonitor) { @@ -628,7 +632,7 @@ public abstract class WMShellModule { dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter, - recentTasksController.orElse(null), interactionJankMonitor); + recentTasksController.orElse(null), interactionJankMonitor, mainHandler); } @WMSingleton @@ -638,7 +642,8 @@ public abstract class WMShellModule { Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, ShellTaskOrganizer shellTaskOrganizer, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context); if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isEnabled(context) @@ -652,7 +657,8 @@ public abstract class WMShellModule { shellTaskOrganizer, maxTaskLimit, interactionJankMonitor, - context) + context, + handler) ); } @@ -699,9 +705,10 @@ public abstract class WMShellModule { static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler( Transitions transitions, Context context, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { return new ExitDesktopTaskTransitionHandler( - transitions, context, interactionJankMonitor); + transitions, context, interactionJankMonitor, handler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 037fbb235bd4..3a4764d45f2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -101,7 +101,8 @@ public abstract class Pip1Module { DisplayInsetsController displayInsetsController, TabletopModeController pipTabletopController, Optional<OneHandedController> oneHandedController, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler handler) { return Optional.ofNullable(PipController.create( context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, @@ -111,7 +112,7 @@ public abstract class Pip1Module { pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, displayInsetsController, pipTabletopController, oneHandedController, - mainExecutor)); + mainExecutor, handler)); } // Handler is used by Icon.loadDrawableAsync diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index c74f4a7854d9..853284a58904 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -35,6 +35,7 @@ import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.os.Binder +import android.os.Handler import android.os.IBinder import android.os.SystemProperties import android.util.Size @@ -136,7 +137,8 @@ class DesktopTasksController( @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, private val recentTasksController: RecentTasksController?, - private val interactionJankMonitor: InteractionJankMonitor + private val interactionJankMonitor: InteractionJankMonitor, + @ShellMainThread private val handler: Handler, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -387,7 +389,7 @@ class DesktopTasksController( taskSurface: SurfaceControl, ) { logV("startDragToDesktop taskId=%d", taskInfo.taskId) - interactionJankMonitor.begin(taskSurface, context, + interactionJankMonitor.begin(taskSurface, context, handler, CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) dragToDesktopTransitionHandler.startDragToDesktopTransition( taskInfo.taskId, @@ -791,7 +793,7 @@ class DesktopTasksController( releaseVisualIndicator() if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) { interactionJankMonitor.begin( - taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable" + taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable" ) // reposition non-resizable app back to its original position before being dragged @@ -804,7 +806,7 @@ class DesktopTasksController( ) } else { interactionJankMonitor.begin( - taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable" + taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable" ) snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position) } @@ -1604,7 +1606,7 @@ class DesktopTasksController( when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { // Start a new jank interaction for the drag release to desktop window animation. - interactionJankMonitor.begin(taskSurface, context, + interactionJankMonitor.begin(taskSurface, context, handler, CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop") finalizeDragToDesktop(taskInfo) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 597637d3fbfc..dae37f4926f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.content.Context +import android.os.Handler import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_BACK @@ -29,6 +30,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver @@ -45,7 +47,8 @@ class DesktopTasksLimiter ( private val shellTaskOrganizer: ShellTaskOrganizer, private val maxTasksLimit: Int, private val interactionJankMonitor: InteractionJankMonitor, - private val context: Context + private val context: Context, + @ShellMainThread private val handler: Handler, ) { private val minimizeTransitionObserver = MinimizeTransitionObserver() @VisibleForTesting @@ -125,7 +128,7 @@ class DesktopTasksLimiter ( if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) { // Begin minimize window CUJ instrumentation. interactionJankMonitor.begin( - mActiveTaskDetails.transitionInfo?.rootLeash, context, + mActiveTaskDetails.transitionInfo?.rootLeash, context, handler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java index e87be520cc91..dedd44f3950a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.util.DisplayMetrics; import android.view.SurfaceControl; @@ -44,6 +45,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.Cuj; import com.android.internal.jank.InteractionJankMonitor; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.transition.Transitions; @@ -63,6 +65,8 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH private final Context mContext; private final Transitions mTransitions; private final InteractionJankMonitor mInteractionJankMonitor; + @ShellMainThread + private final Handler mHandler; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; @@ -71,20 +75,24 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH public ExitDesktopTaskTransitionHandler( Transitions transitions, Context context, - InteractionJankMonitor interactionJankMonitor - ) { - this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor); + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler + ) { + this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor, + handler); } private ExitDesktopTaskTransitionHandler( Transitions transitions, Supplier<SurfaceControl.Transaction> supplier, Context context, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { mTransitions = transitions; mTransactionSupplier = supplier; mContext = context; mInteractionJankMonitor = interactionJankMonitor; + mHandler = handler; } /** @@ -154,7 +162,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH final SurfaceControl sc = change.getLeash(); final Rect endBounds = change.getEndAbsBounds(); mInteractionJankMonitor - .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE); + .begin(sc, mContext, mHandler, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE); // Hide the first (fullscreen) frame because the animation will start from the freeform // size. startT.hide(sc) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 832e2d2bc77b..517e20910f6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -28,6 +28,7 @@ import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.util.ArrayMap; import android.view.SurfaceControl; @@ -43,6 +44,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -65,6 +67,8 @@ public class FreeformTaskTransitionHandler private final InteractionJankMonitor mInteractionJankMonitor; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; + @ShellMainThread + private final Handler mHandler; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); @@ -79,7 +83,8 @@ public class FreeformTaskTransitionHandler ShellExecutor mainExecutor, ShellExecutor animExecutor, DesktopModeTaskRepository desktopModeTaskRepository, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { mTransitions = transitions; mContext = context; mWindowDecorViewModel = windowDecorViewModel; @@ -88,6 +93,7 @@ public class FreeformTaskTransitionHandler mInteractionJankMonitor = interactionJankMonitor; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; + mHandler = handler; if (Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } @@ -267,7 +273,7 @@ public class FreeformTaskTransitionHandler change.getTaskInfo().displayId) == 1) { // Starting the jank trace if closing the last window in desktop mode. mInteractionJankMonitor.begin( - sc, mContext, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); + sc, mContext, mHandler, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); } animator.addListener( new AnimatorListenerAdapter() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 1827923aad90..15472ebc149b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -55,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.shared.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -203,7 +204,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger, - ShellExecutor mainExecutor, Handler mainHandler) { + ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil(); OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context); OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); @@ -217,7 +218,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mainExecutor); OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( context, displayLayout, settingsUtil, animationController, tutorialHandler, - jankMonitor, mainExecutor); + jankMonitor, mainExecutor, mainHandler); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); return new OneHandedController(context, shellInit, shellCommandHandler, shellController, displayController, organizer, touchHandler, tutorialHandler, settingsUtil, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index d157ca837608..95e633d0b5ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -23,6 +23,7 @@ import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSI import android.content.Context; import android.graphics.Rect; +import android.os.Handler; import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; @@ -42,6 +43,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.io.PrintWriter; import java.util.ArrayList; @@ -70,6 +72,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { private final OneHandedSettingsUtil mOneHandedSettingsUtil; private final InteractionJankMonitor mJankMonitor; private final Context mContext; + @ShellMainThread + private final Handler mHandler; private boolean mIsReady; private float mLastVisualOffset = 0; @@ -136,9 +140,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { OneHandedAnimationController animationController, OneHandedTutorialHandler tutorialHandler, InteractionJankMonitor jankMonitor, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, + @ShellMainThread Handler handler) { super(mainExecutor); mContext = context; + mHandler = handler; setDisplayLayout(displayLayout); mOneHandedSettingsUtil = oneHandedSettingsUtil; mAnimationController = animationController; @@ -333,7 +339,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { getDisplayAreaTokenMap().entrySet().iterator().next(); final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withSurface( - cujType, mContext, firstEntry.getValue()); + cujType, mContext, firstEntry.getValue(), mHandler); if (!TextUtils.isEmpty(tag)) { builder.setTag(tag); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index deb7691f2d4d..af6844262771 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -44,6 +44,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; +import android.os.Handler; import android.os.RemoteException; import android.os.SystemProperties; import android.util.Pair; @@ -93,6 +94,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -146,6 +148,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb private Optional<OneHandedController> mOneHandedController; private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; + @ShellMainThread + private final Handler mHandler; protected final PipImpl mImpl; private final Rect mTmpInsetBounds = new Rect(); @@ -405,7 +409,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb DisplayInsetsController displayInsetsController, TabletopModeController pipTabletopController, Optional<OneHandedController> oneHandedController, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, + Handler handler) { if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Device doesn't support Pip feature", TAG); @@ -418,7 +423,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, - displayInsetsController, pipTabletopController, oneHandedController, mainExecutor) + displayInsetsController, pipTabletopController, oneHandedController, mainExecutor, + handler) .mImpl; } @@ -446,11 +452,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb DisplayInsetsController displayInsetsController, TabletopModeController tabletopModeController, Optional<OneHandedController> oneHandedController, - ShellExecutor mainExecutor + ShellExecutor mainExecutor, + @ShellMainThread Handler handler ) { mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; + mHandler = handler; mImpl = new PipImpl(); mWindowManagerShellWrapper = windowManagerShellWrapper; mDisplayController = displayController; @@ -1047,7 +1055,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Begin InteractionJankMonitor with PIP transition CUJs final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withSurface( - CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl()) + CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl(), + mHandler) .setTag(getTransitionTag(direction)) .setTimeout(2000); InteractionJankMonitor.getInstance().begin(builder); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index c660000e4f61..8077aeebf27f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -20,9 +20,12 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; +import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -41,6 +44,7 @@ import android.app.PendingIntent; import android.content.Intent; import android.graphics.Color; import android.graphics.Rect; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -64,6 +68,7 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -79,10 +84,15 @@ import java.util.function.Consumer; * Handles the Recents (overview) animation. Only one of these can run at a time. A recents * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored. */ -public class RecentsTransitionHandler implements Transitions.TransitionHandler { +public class RecentsTransitionHandler implements Transitions.TransitionHandler, + Transitions.TransitionObserver { private static final String TAG = "RecentsTransitionHandler"; + // A placeholder for a synthetic transition that isn't backed by a true system transition + public static final IBinder SYNTHETIC_TRANSITION = new Binder(); + private final Transitions mTransitions; + private final ShellTaskOrganizer mShellTaskOrganizer; private final ShellExecutor mExecutor; @Nullable private final RecentTasksController mRecentTasksController; @@ -99,19 +109,26 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private final HomeTransitionObserver mHomeTransitionObserver; private @Nullable Color mBackgroundColor; - public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, + public RecentsTransitionHandler( + @NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, @Nullable RecentTasksController recentTasksController, - HomeTransitionObserver homeTransitionObserver) { + @NonNull HomeTransitionObserver homeTransitionObserver) { + mShellTaskOrganizer = shellTaskOrganizer; mTransitions = transitions; mExecutor = transitions.getMainExecutor(); mRecentTasksController = recentTasksController; mHomeTransitionObserver = homeTransitionObserver; if (!Transitions.ENABLE_SHELL_TRANSITIONS) return; if (recentTasksController == null) return; - shellInit.addInitCallback(() -> { - recentTasksController.setTransitionHandler(this); - transitions.addHandler(this); - }, this); + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mRecentTasksController.setTransitionHandler(this); + mTransitions.addHandler(this); + mTransitions.registerObserver(this); } /** Register a mixer handler. {@see RecentsMixedHandler}*/ @@ -138,17 +155,59 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mBackgroundColor = color; } + /** + * Starts a new real/synthetic recents transition. + */ @VisibleForTesting public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener) { + // only care about latest one. + mAnimApp = appThread; + + // TODO(b/366021931): Formalize this later + final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition"); + if (isSyntheticRequest) { + return startSyntheticRecentsTransition(listener); + } else { + return startRealRecentsTransition(intent, fillIn, options, listener); + } + } + + /** + * Starts a synthetic recents transition that is not backed by a real WM transition. + */ + private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsTransitionHandler.startRecentsTransition(synthetic)"); + final RecentsController lastController = getLastController(); + if (lastController != null) { + lastController.cancel(lastController.isSyntheticTransition() + ? "existing_running_synthetic_transition" + : "existing_running_transition"); + return null; + } + + // Create a new synthetic transition and start it immediately + final RecentsController controller = new RecentsController(listener); + controller.startSyntheticTransition(); + mControllers.add(controller); + return SYNTHETIC_TRANSITION; + } + + /** + * Starts a real WM-backed recents transition. + */ + private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, + IRecentsAnimationRunner listener) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsTransitionHandler.startRecentsTransition"); - // only care about latest one. - mAnimApp = appThread; - WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.sendPendingIntent(intent, fillIn, options); - final RecentsController controller = new RecentsController(listener); + + // Find the mixed handler which should handle this request (if we are in a state where a + // mixed handler is needed). This is slightly convoluted because starting the transition + // requires the handler, but the mixed handler also needs a reference to the transition. RecentsMixedHandler mixer = null; Consumer<IBinder> setTransitionForMixer = null; for (int i = 0; i < mMixers.size(); ++i) { @@ -160,12 +219,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, mixer == null ? this : mixer); - for (int i = 0; i < mStateListeners.size(); i++) { - mStateListeners.get(i).onTransitionStarted(transition); - } if (mixer != null) { setTransitionForMixer.accept(transition); } + + final RecentsController controller = new RecentsController(listener); if (transition != null) { controller.setTransition(transition); mControllers.add(controller); @@ -187,11 +245,28 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return null; } - private int findController(IBinder transition) { + /** + * Returns if there is currently a pending or active recents transition. + */ + @Nullable + private RecentsController getLastController() { + return !mControllers.isEmpty() ? mControllers.getLast() : null; + } + + /** + * Finds an existing controller for the provided {@param transition}, or {@code null} if none + * exists. + */ + @Nullable + @VisibleForTesting + RecentsController findController(@NonNull IBinder transition) { for (int i = mControllers.size() - 1; i >= 0; --i) { - if (mControllers.get(i).mTransition == transition) return i; + final RecentsController controller = mControllers.get(i); + if (controller.mTransition == transition) { + return controller; + } } - return -1; + return null; } @Override @@ -199,13 +274,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, Transitions.TransitionFinishCallback finishCallback) { - final int controllerIdx = findController(transition); - if (controllerIdx < 0) { + final RecentsController controller = findController(transition); + if (controller == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsTransitionHandler.startAnimation: no controller found"); return false; } - final RecentsController controller = mControllers.get(controllerIdx); final IApplicationThread animApp = mAnimApp; mAnimApp = null; if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { @@ -221,13 +295,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { - final int targetIdx = findController(mergeTarget); - if (targetIdx < 0) { + final RecentsController controller = findController(mergeTarget); + if (controller == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsTransitionHandler.mergeAnimation: no controller found"); return; } - final RecentsController controller = mControllers.get(targetIdx); controller.merge(info, t, finishCallback); } @@ -244,8 +317,21 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } + @Override + public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + RecentsController controller = findController(SYNTHETIC_TRANSITION); + if (controller != null) { + // Cancel the existing synthetic transition if there is one + controller.cancel("incoming_transition"); + } + } + /** There is only one of these and it gets reset on finish. */ - private class RecentsController extends IRecentsAnimationController.Stub { + @VisibleForTesting + class RecentsController extends IRecentsAnimationController.Stub { + private final int mInstanceId; private IRecentsAnimationRunner mListener; @@ -307,7 +393,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mDeathHandler = () -> { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.DeathRecipient: binder died", mInstanceId); - finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */); + finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */, + "deathRecipient"); }; try { mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */); @@ -317,6 +404,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } + /** + * Sets the started transition for this instance of the recents transition. + */ void setTransition(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition); @@ -330,6 +420,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } void cancel(boolean toHome, boolean withScreenshots, String reason) { + if (cancelSyntheticTransition(reason)) { + return; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.cancel: toHome=%b reason=%s", mInstanceId, toHome, reason); @@ -341,7 +435,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } if (mFinishCB != null) { - finishInner(toHome, false /* userLeave */, null /* finishCb */); + finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel"); } else { cleanUp(); } @@ -436,6 +530,91 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } + /** + * Starts a new transition that is not backed by a system transition. + */ + void startSyntheticTransition() { + mTransition = SYNTHETIC_TRANSITION; + + // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as + // both opening and closing since there's some pre-existing + // dependencies on having a closing task + final ActivityManager.RunningTaskInfo homeTask = + mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream() + .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME) + .findFirst() + .get(); + final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget( + homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN, + 0, true /* isTranslucent */); + final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget( + homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE, + 0, true /* isTranslucent */); + final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); + apps.add(openingTarget); + apps.add(closingTarget); + try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.start: calling onAnimationStart with %d apps", + mInstanceId, apps.size()); + mListener.onAnimationStart(this, + apps.toArray(new RemoteAnimationTarget[apps.size()]), + new RemoteAnimationTarget[0], + new Rect(0, 0, 0, 0), new Rect(), new Bundle()); + for (int i = 0; i < mStateListeners.size(); i++) { + mStateListeners.get(i).onAnimationStateChanged(true); + } + } catch (RemoteException e) { + Slog.e(TAG, "Error starting recents animation", e); + cancel("startSynthetricTransition() failed"); + } + } + + /** + * Returns whether this transition is backed by a real system transition or not. + */ + boolean isSyntheticTransition() { + return mTransition == SYNTHETIC_TRANSITION; + } + + /** + * Called when a synthetic transition is canceled. + */ + boolean cancelSyntheticTransition(String reason) { + if (!isSyntheticTransition()) { + return false; + } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.cancelSyntheticTransition reason=%s", + mInstanceId, reason); + try { + // TODO(b/366021931): Notify the correct tasks once we build actual targets, and + // clean up leashes accordingly + mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]); + } catch (RemoteException e) { + Slog.e(TAG, "Error canceling previous recents animation", e); + } + cleanUp(); + return true; + } + + /** + * Called when a synthetic transition is finished. + * @return + */ + boolean finishSyntheticTransition() { + if (!isSyntheticTransition()) { + return false; + } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.finishSyntheticTransition", mInstanceId); + // TODO(b/366021931): Clean up leashes accordingly + cleanUp(); + return true; + } + boolean start(TransitionInfo info, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -662,7 +841,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { // Set the callback once again so we can finish correctly. mFinishCB = finishCB; finishInner(true /* toHome */, false /* userLeave */, - null /* finishCb */); + null /* finishCb */, "takeOverAnimation"); }, updatedStates); }); } @@ -810,7 +989,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { sendCancelWithSnapshots(); mExecutor.executeDelayed( () -> finishInner(true /* toHome */, false /* userLeaveHint */, - null /* finishCb */), 0); + null /* finishCb */, "merge"), 0); return; } if (recentsOpening != null) { @@ -1005,7 +1184,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return; } final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId() - : Display.DEFAULT_DISPLAY; + : DEFAULT_DISPLAY; // transient launches don't receive focus automatically. Since we are taking over // the gesture now, take focus explicitly. // This also moves recents back to top if the user gestured before a switch @@ -1038,11 +1217,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { @Override @SuppressLint("NewApi") public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) { - mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb)); + mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb, + "requested")); } private void finishInner(boolean toHome, boolean sendUserLeaveHint, - IResultReceiver runnerFinishCb) { + IResultReceiver runnerFinishCb, String reason) { + if (finishSyntheticTransition()) { + return; + } + if (mFinishCB == null) { Slog.e(TAG, "Duplicate call to finish"); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java index e8733ebd8f03..95874c8193c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java @@ -24,7 +24,4 @@ public interface RecentsTransitionStateListener { /** Notifies whether the recents animation is running. */ default void onAnimationStateChanged(boolean running) { } - - /** Notifies that a recents shell transition has started. */ - default void onTransitionStarted(IBinder transition) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 4ba84eeb2e6d..5a905cfd317f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1654,7 +1654,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, mRootTaskInfo.configuration, this, mParentContainerCallbacks, mDisplayController, mDisplayImeController, mTaskOrganizer, - PARALLAX_ALIGN_CENTER /* parallaxType */); + PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 2c02d4f8bd1a..d03832d3e85e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -1501,16 +1501,16 @@ public class Transitions implements RemoteCallable<Transitions>, * transition animation. The Transition system will apply it when * finishCallback is called by the transition handler. */ - void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info, + default void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction); + @NonNull SurfaceControl.Transaction finishTransaction) {} /** * Called when the transition is starting to play. It isn't called for merged transitions. * * @param transition the unique token of this transition */ - void onTransitionStarting(@NonNull IBinder transition); + default void onTransitionStarting(@NonNull IBinder transition) {} /** * Called when a transition is merged into another transition. There won't be any following @@ -1519,7 +1519,7 @@ public class Transitions implements RemoteCallable<Transitions>, * @param merged the unique token of the transition that's merged to another one * @param playing the unique token of the transition that accepts the merge */ - void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing); + default void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {} /** * Called when the transition is finished. This isn't called for merged transitions. @@ -1527,7 +1527,7 @@ public class Transitions implements RemoteCallable<Transitions>, * @param transition the unique token of this transition * @param aborted {@code true} if this transition is aborted; {@code false} otherwise. */ - void onTransitionFinished(@NonNull IBinder transition, boolean aborted); + default void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {} } @BinderThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index b14283f878a3..02c818ffa906 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -111,6 +111,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; @@ -154,7 +155,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final ShellTaskOrganizer mTaskOrganizer; private final ShellController mShellController; private final Context mContext; - private final Handler mMainHandler; + private final @ShellMainThread Handler mMainHandler; private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; @@ -214,7 +215,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { public DesktopModeWindowDecorViewModel( Context context, ShellExecutor shellExecutor, - Handler mainHandler, + @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, @@ -270,7 +271,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DesktopModeWindowDecorViewModel( Context context, ShellExecutor shellExecutor, - Handler mainHandler, + @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, @@ -495,7 +496,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } mInteractionJankMonitor.begin( - decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source); + decoration.mTaskSurface, mContext, mMainHandler, + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source); mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); @@ -512,7 +514,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Toast.makeText(mContext, R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show(); } else { - mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, + mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler, Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable"); mDesktopTasksController.snapToHalfScreen( decoration.mTaskInfo, @@ -548,7 +550,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } final WindowContainerTransaction wct = new WindowContainerTransaction(); - mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, + mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler, CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU); // App sometimes draws before the insets from WindowDecoration#relayout have // been added, so they must be added here @@ -860,6 +862,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { handleCaptionThroughStatusBar(e, decoration); final boolean wasDragging = mIsDragging; updateDragStatus(e.getActionMasked()); + final boolean upOrCancel = e.getActionMasked() == ACTION_UP + || e.getActionMasked() == ACTION_CANCEL; + if (wasDragging && upOrCancel) { + // When finishing a drag the event will be consumed, which means the pressed + // state of the App Handle must be manually reset to scale its drawable back to + // its original shape. This is necessary for drag gestures of the Handle that + // result in a cancellation (dragging back to the top). + v.setPressed(false); + } // Only prevent onClick from receiving this event if it's a drag. return wasDragging; } @@ -1378,7 +1389,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragStartListener, mTransitions, mInteractionJankMonitor, - mTransactionFactory); + mTransactionFactory, + mMainHandler); windowDecoration.setTaskDragResizer(taskPositioner); final DesktopModeTouchEventListener touchEventListener = @@ -1602,7 +1614,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DragPositioningCallbackUtility.DragStartListener dragStartListener, Transitions transitions, InteractionJankMonitor interactionJankMonitor, - Supplier<SurfaceControl.Transaction> transactionFactory) { + Supplier<SurfaceControl.Transaction> transactionFactory, + Handler handler) { final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled() ? new VeiledResizeTaskPositioner( taskOrganizer, @@ -1610,7 +1623,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { displayController, dragStartListener, transitions, - interactionJankMonitor) + interactionJankMonitor, + handler) : new FluidResizeTaskPositioner( taskOrganizer, transitions, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index d43ee4425e6b..16036bee75b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -24,6 +24,8 @@ import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON; @@ -604,13 +606,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // their custom content. relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; } else { - if (Flags.enableCaptionCompatInsetForceConsumption()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { // Force-consume the caption bar insets when the app tries to hide the caption. // This improves app compatibility of immersive apps. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING; } } - if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()) { // Always force-consume the caption bar insets for maximum app compatibility, // including non-immersive apps that just don't handle caption insets properly. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; @@ -753,9 +755,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */); final IconProvider provider = new IconProvider(mContext); final Drawable appIconDrawable = provider.getIcon(activityInfo); + final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, + UserHandle.of(mTaskInfo.userId)); final BaseIconFactory headerIconFactory = createIconFactory(mContext, R.dimen.desktop_mode_caption_icon_radius); - mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT); + mAppIconBitmap = headerIconFactory.createIconBitmap(badgedAppIconDrawable, + 1f /* scale */); final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext, R.dimen.desktop_mode_resize_veil_icon_size); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 599815530f63..6f3f41191485 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -24,6 +24,7 @@ import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.view.Surface; import android.view.SurfaceControl; @@ -37,6 +38,7 @@ import androidx.annotation.Nullable; import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.transition.Transitions; import java.util.function.Supplier; @@ -63,14 +65,17 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T private int mCtrlType; private boolean mIsResizingOrAnimatingResize; @Surface.Rotation private int mRotation; + @ShellMainThread + private final Handler mHandler; public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Transitions transitions, InteractionJankMonitor interactionJankMonitor) { + Transitions transitions, InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { this(taskOrganizer, windowDecoration, displayController, dragStartListener, - SurfaceControl.Transaction::new, transitions, interactionJankMonitor); + SurfaceControl.Transaction::new, transitions, interactionJankMonitor, handler); } public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, @@ -78,7 +83,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, Supplier<SurfaceControl.Transaction> supplier, Transitions transitions, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { mDesktopWindowDecoration = windowDecoration; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; @@ -86,6 +91,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T mTransactionSupplier = supplier; mTransitions = transitions; mInteractionJankMonitor = interactionJankMonitor; + mHandler = handler; } @Override @@ -97,7 +103,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T if (isResizing()) { // Capture CUJ for re-sizing window in DW mode. mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, - mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW); + mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW); if (!mDesktopWindowDecoration.mTaskInfo.isFocused) { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true); @@ -131,7 +137,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { // Begin window drag CUJ instrumentation only when drag position moves. mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, - mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW); + mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW); final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index b53ea3837178..227060d15640 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -137,6 +137,8 @@ public class BackAnimationControllerTest extends ShellTestCase { private Transitions mTransitions; @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + @Mock + private Handler mHandler; private BackAnimationController mController; private TestableContentResolver mContentResolver; @@ -161,13 +163,14 @@ public class BackAnimationControllerTest extends ShellTestCase { mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext, - mAnimationBackground, mRootTaskDisplayAreaOrganizer); - mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground); + mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler); + mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground, + mHandler); mShellBackAnimationRegistry = new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation, mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null, new CustomCrossActivityBackAnimation(mContext, mAnimationBackground, - mRootTaskDisplayAreaOrganizer), + mRootTaskDisplayAreaOrganizer, mHandler), /* defaultBackToHomeAnimation= */ null); mController = new BackAnimationController( @@ -181,7 +184,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mAnimationBackground, mShellBackAnimationRegistry, mShellCommandHandler, - mTransitions); + mTransitions, + mHandler); mShellInit.init(); mShellExecutor.flushAll(); mTouchableRegion = new Rect(0, 0, 100, 100); @@ -344,7 +348,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mAnimationBackground, mShellBackAnimationRegistry, mShellCommandHandler, - mTransitions); + mTransitions, + mHandler); shellInit.init(); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); @@ -898,7 +903,8 @@ public class BackAnimationControllerTest extends ShellTestCase { new BackAnimationRunner( mAnimatorCallback, mBackAnimationRunner, - mContext)); + mContext, + mHandler)); } private void unregisterAnimation(int type) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt index 080ad901c656..5b5ef6f48789 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt @@ -22,6 +22,7 @@ import android.app.WindowConfiguration import android.graphics.Color import android.graphics.Point import android.graphics.Rect +import android.os.Handler import android.os.RemoteException import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -66,6 +67,7 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() { @Mock private lateinit var transitionAnimation: TransitionAnimation @Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo @Mock private lateinit var transaction: Transaction + @Mock private lateinit var handler: Handler private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation private lateinit var customAnimationLoader: CustomAnimationLoader @@ -80,7 +82,8 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() { backAnimationBackground, rootTaskDisplayAreaOrganizer, transaction, - customAnimationLoader + customAnimationLoader, + handler, ) whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID))) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java index f5847cc27071..cf69704a0470 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Handler; import android.os.SystemClock; import android.provider.DeviceConfig; import android.view.InputDevice; @@ -55,6 +56,7 @@ public class DividerViewTest extends ShellTestCase { private @Mock DisplayController mDisplayController; private @Mock DisplayImeController mDisplayImeController; private @Mock ShellTaskOrganizer mTaskOrganizer; + private @Mock Handler mHandler; private SplitLayout mSplitLayout; private DividerView mDividerView; @@ -65,7 +67,7 @@ public class DividerViewTest extends ShellTestCase { Configuration configuration = getConfiguration(); mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration, mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController, - mTaskOrganizer, SplitLayout.PARALLAX_NONE); + mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler); SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager", mContext, configuration, mCallbacks); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 82b3a7de521b..177e47a342f6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Handler; import android.window.WindowContainerTransaction; import androidx.test.annotation.UiThreadTest; @@ -65,6 +66,7 @@ public class SplitLayoutTests extends ShellTestCase { @Mock DisplayImeController mDisplayImeController; @Mock ShellTaskOrganizer mTaskOrganizer; @Mock WindowContainerTransaction mWct; + @Mock Handler mHandler; @Captor ArgumentCaptor<Runnable> mRunnableCaptor; private SplitLayout mSplitLayout; @@ -80,7 +82,8 @@ public class SplitLayoutTests extends ShellTestCase { mDisplayController, mDisplayImeController, mTaskOrganizer, - SplitLayout.PARALLAX_NONE)); + SplitLayout.PARALLAX_NONE, + mHandler)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index b47201e46759..e610ebd6bfab 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -40,6 +40,7 @@ import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.os.Binder +import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -181,6 +182,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var mockInteractionJankMonitor: InteractionJankMonitor @Mock private lateinit var mockSurface: SurfaceControl @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener + @Mock private lateinit var mockHandler: Handler private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -221,7 +223,8 @@ class DesktopTasksControllerTest : ShellTestCase() { shellTaskOrganizer, MAX_TASK_LIMIT, mockInteractionJankMonitor, - mContext) + mContext, + mockHandler) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } @@ -274,7 +277,9 @@ class DesktopTasksControllerTest : ShellTestCase() { shellExecutor, Optional.of(desktopTasksLimiter), recentTasksController, - mockInteractionJankMonitor) + mockInteractionJankMonitor, + mockHandler, + ) } @After diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 2d0e428c45cb..61d03cac035c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.os.Binder +import android.os.Handler import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY @@ -70,6 +71,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer @Mock lateinit var transitions: Transitions @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var handler: Handler private lateinit var mockitoSession: StaticMockitoSession private lateinit var desktopTasksLimiter: DesktopTasksLimiter @@ -85,7 +87,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter = DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) } @After @@ -97,7 +99,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) } } @@ -105,7 +107,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) } } @@ -334,7 +336,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun getTaskToMinimizeIfNeeded_tasksAboveLimit_otherLimit_returnsBackTask() { desktopTasksLimiter = DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() } val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded( @@ -375,6 +377,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).begin( any(), eq(mContext), + eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) desktopTasksLimiter.getTransitionObserver().onTransitionFinished( @@ -403,7 +406,9 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).begin( any(), eq(mContext), - eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) + eq(handler), + eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW), + ) desktopTasksLimiter.getTransitionObserver().onTransitionFinished( transition, @@ -432,6 +437,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).begin( any(), eq(mContext), + eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) desktopTasksLimiter.getTransitionObserver().onTransitionMerged( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java index e0463b41ad20..fefa933c5208 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java @@ -34,6 +34,7 @@ import android.app.WindowConfiguration; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.os.Handler; import android.os.IBinder; import android.util.DisplayMetrics; import android.view.SurfaceControl; @@ -81,6 +82,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { Transitions.TransitionFinishCallback mTransitionFinishCallback; @Mock ShellExecutor mExecutor; + @Mock + Handler mHandler; private Point mPoint; private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler; @@ -97,7 +100,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { .thenReturn(getContext().getResources().getDisplayMetrics()); mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions, - mContext, mInteractionJankMonitor); + mContext, mInteractionJankMonitor, mHandler); mPoint = new Point(0, 0); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index 9c7f7237871a..9146906b6385 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Display; @@ -100,6 +101,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { OneHandedSettingsUtil mMockSettingsUitl; @Mock InteractionJankMonitor mJankMonitor; + @Mock + Handler mMockHandler; List<DisplayAreaAppearedInfo> mDisplayAreaAppearedInfoList = new ArrayList<>(); @@ -142,7 +145,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockAnimationController, mTutorialHandler, mJankMonitor, - mMockShellMainExecutor)); + mMockShellMainExecutor, + mMockHandler)); for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) { mDisplayAreaAppearedInfoList.add(getDummyDisplayAreaInfo()); @@ -429,7 +433,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockAnimationController, mTutorialHandler, mJankMonitor, - mMockShellMainExecutor)); + mMockShellMainExecutor, + mMockHandler)); assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index f3944d5ac352..96003515a485 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -40,6 +40,7 @@ import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -115,6 +116,7 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder; @Mock private DisplayInsetsController mMockDisplayInsetsController; @Mock private TabletopModeController mMockTabletopModeController; + @Mock private Handler mMockHandler; @Mock private DisplayLayout mMockDisplayLayout1; @Mock private DisplayLayout mMockDisplayLayout2; @@ -138,7 +140,7 @@ public class PipControllerTest extends ShellTestCase { mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mMockPipParamsChangedForwarder, mMockDisplayInsetsController, mMockTabletopModeController, - mMockOneHandedController, mMockExecutor); + mMockOneHandedController, mMockExecutor, mMockHandler); mShellInit.init(); when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm); when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper); @@ -230,7 +232,7 @@ public class PipControllerTest extends ShellTestCase { mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mMockPipParamsChangedForwarder, mMockDisplayInsetsController, mMockTabletopModeController, - mMockOneHandedController, mMockExecutor)); + mMockOneHandedController, mMockExecutor, mMockHandler)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java new file mode 100644 index 000000000000..769acf7fdfde --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.recents; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityTaskManager; +import android.app.IApplicationThread; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.HomeTransitionObserver; +import com.android.wm.shell.transition.Transitions; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.quality.Strictness; + +import java.util.Optional; + +/** + * Tests for {@link RecentTasksController} + * + * Usage: atest WMShellUnitTests:RecentsTransitionHandlerTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RecentsTransitionHandlerTest extends ShellTestCase { + + @Mock + private Context mContext; + @Mock + private TaskStackListenerImpl mTaskStackListener; + @Mock + private ShellCommandHandler mShellCommandHandler; + @Mock + private DesktopModeTaskRepository mDesktopModeTaskRepository; + @Mock + private ActivityTaskManager mActivityTaskManager; + @Mock + private DisplayInsetsController mDisplayInsetsController; + @Mock + private IRecentTasksListener mRecentTasksListener; + @Mock + private TaskStackTransitionObserver mTaskStackTransitionObserver; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private ShellTaskOrganizer mShellTaskOrganizer; + private RecentTasksController mRecentTasksController; + private RecentTasksController mRecentTasksControllerReal; + private RecentsTransitionHandler mRecentsTransitionHandler; + private ShellInit mShellInit; + private ShellController mShellController; + private TestShellExecutor mMainExecutor; + private static StaticMockitoSession sMockitoSession; + + @Before + public void setUp() { + sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT) + .mockStatic(DesktopModeStatus.class).startMocking(); + ExtendedMockito.doReturn(true) + .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); + + mMainExecutor = new TestShellExecutor(); + when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); + when(mContext.getSystemService(KeyguardManager.class)) + .thenReturn(mock(KeyguardManager.class)); + mShellInit = spy(new ShellInit(mMainExecutor)); + mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler, + mDisplayInsetsController, mMainExecutor)); + mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, + mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, + Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver, + mMainExecutor); + mRecentTasksController = spy(mRecentTasksControllerReal); + mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, + null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController), + mMainExecutor); + + final Transitions transitions = mock(Transitions.class); + doReturn(mMainExecutor).when(transitions).getMainExecutor(); + mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer, + transitions, mRecentTasksController, mock(HomeTransitionObserver.class)); + + mShellInit.init(); + } + + @After + public void tearDown() { + sMockitoSession.finishMocking(); + } + + @Test + public void testStartSyntheticRecentsTransition_callsOnAnimationStart() throws Exception { + final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class); + doReturn(new Binder()).when(runner).asBinder(); + Bundle options = new Bundle(); + options.putBoolean("is_synthetic_recents_transition", true); + IBinder transition = mRecentsTransitionHandler.startRecentsTransition( + mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), + runner); + verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any()); + + // Finish and verify no transition remains + mRecentsTransitionHandler.findController(transition).finish(true /* toHome */, + false /* sendUserLeaveHint */, null /* finishCb */); + mMainExecutor.flushAll(); + assertNull(mRecentsTransitionHandler.findController(transition)); + } + + @Test + public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception { + final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class); + doReturn(new Binder()).when(runner).asBinder(); + Bundle options = new Bundle(); + options.putBoolean("is_synthetic_recents_transition", true); + IBinder transition = mRecentsTransitionHandler.startRecentsTransition( + mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), + runner); + verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any()); + + mRecentsTransitionHandler.findController(transition).cancel("test"); + mMainExecutor.flushAll(); + verify(runner).onAnimationCanceled(any(), any()); + assertNull(mRecentsTransitionHandler.findController(transition)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 61a725f5701d..fec9e3ebd1ef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1211,7 +1211,7 @@ public class ShellTransitionTests extends ShellTestCase { mTransactionPool, createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class)); final RecentsTransitionHandler recentsHandler = - new RecentsTransitionHandler(shellInit, transitions, + new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions, mock(RecentTasksController.class), mock(HomeTransitionObserver.class)); transitions.replaceDefaultHandlerForTest(mDefaultHandler); shellInit.init(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index a18fbf0891ef..85bc7cc287e6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -247,7 +247,18 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS) whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor) - whenever(mockTaskPositionerFactory.create(any(), any(), any(), any(), any(), any(), any())) + whenever( + mockTaskPositionerFactory.create( + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any() + ) + ) .thenReturn(mockTaskPositioner) doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 7784af6b1111..ab41d9c80177 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.res.Resources import android.graphics.Point import android.graphics.Rect +import android.os.Handler import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.Display @@ -107,6 +108,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockResources: Resources @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + @Mock + private lateinit var mockHandler: Handler private lateinit var taskPositioner: VeiledResizeTaskPositioner @@ -155,7 +158,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { mockDragStartListener, mockTransactionFactory, mockTransitions, - mockInteractionJankMonitor + mockInteractionJankMonitor, + mockHandler, ) } diff --git a/libs/hwui/platform/host/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h index d709430cc9b6..b4e7bd34971a 100644 --- a/libs/hwui/platform/host/thread/ThreadBase.h +++ b/libs/hwui/platform/host/thread/ThreadBase.h @@ -48,7 +48,7 @@ protected: nsecs_t nextWakeup = mQueue.nextWakeup(lock); std::chrono::nanoseconds duration = std::chrono::nanoseconds::max(); if (nextWakeup < std::numeric_limits<nsecs_t>::max()) { - int timeout = nextWakeup - WorkQueue::clock::now(); + nsecs_t timeout = nextWakeup - WorkQueue::clock::now(); if (timeout < 0) timeout = 0; duration = std::chrono::nanoseconds(timeout); } diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 1afef75bc741..d993b8715260 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -64,25 +64,6 @@ MouseCursorController::~MouseCursorController() { mLocked.pointerSprite.clear(); } -std::optional<FloatRect> MouseCursorController::getBounds() const { - std::scoped_lock lock(mLock); - - return getBoundsLocked(); -} - -std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) { - if (!mLocked.viewport.isValid()) { - return {}; - } - - return FloatRect{ - static_cast<float>(mLocked.viewport.logicalLeft), - static_cast<float>(mLocked.viewport.logicalTop), - static_cast<float>(mLocked.viewport.logicalRight - 1), - static_cast<float>(mLocked.viewport.logicalBottom - 1), - }; -} - void MouseCursorController::move(float deltaX, float deltaY) { #if DEBUG_MOUSE_CURSOR_UPDATES ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); @@ -105,11 +86,20 @@ void MouseCursorController::setPosition(float x, float y) { } void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) { - const auto bounds = getBoundsLocked(); - if (!bounds) return; + const auto& v = mLocked.viewport; + if (!v.isValid()) return; - mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x)); - mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y)); + // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside + // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily + // close to the outside edge. + const FloatRect bounds{ + static_cast<float>(mLocked.viewport.logicalLeft), + static_cast<float>(mLocked.viewport.logicalTop), + static_cast<float>(mLocked.viewport.logicalRight - 1), + static_cast<float>(mLocked.viewport.logicalBottom - 1), + }; + mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x)); + mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y)); updatePointerLocked(); } @@ -216,9 +206,11 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, // Reset cursor position to center if size or display changed. if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth || oldDisplayHeight != newDisplayHeight) { - if (const auto bounds = getBoundsLocked(); bounds) { - mLocked.pointerX = (bounds->left + bounds->right) * 0.5f; - mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f; + if (viewport.isValid()) { + // Use integer coordinates as the starting point for the cursor location. + // We usually expect display sizes to be even numbers, so the flooring is precautionary. + mLocked.pointerX = std::floor((viewport.logicalLeft + viewport.logicalRight) / 2); + mLocked.pointerY = std::floor((viewport.logicalTop + viewport.logicalBottom) / 2); // Reload icon resources for density may be changed. loadResourcesLocked(getAdditionalMouseResources); } else { diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 860034141a0b..12b31a8c531a 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -43,7 +43,6 @@ public: MouseCursorController(PointerControllerContext& context); ~MouseCursorController(); - std::optional<FloatRect> getBounds() const; void move(float deltaX, float deltaY); void setPosition(float x, float y); FloatPoint getPosition() const; @@ -104,7 +103,6 @@ private: } mLocked GUARDED_BY(mLock); - std::optional<FloatRect> getBoundsLocked() const; void setPositionLocked(float x, float y); void updatePointerLocked(); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 5ae967bc369a..78d7d3a7051b 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -138,10 +138,6 @@ std::mutex& PointerController::getLock() const { return mDisplayInfoListener->mLock; } -std::optional<FloatRect> PointerController::getBounds() const { - return mCursorController.getBounds(); -} - void PointerController::move(float deltaX, float deltaY) { const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 4d1e1d733cc1..ee8d1211341f 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -51,7 +51,6 @@ public: ~PointerController() override; - std::optional<FloatRect> getBounds() const override; void move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; FloatPoint getPosition() const override; @@ -166,9 +165,6 @@ public: ~TouchPointerController() override; - std::optional<FloatRect> getBounds() const override { - LOG_ALWAYS_FATAL("Should not be called"); - } void move(float, float) override { LOG_ALWAYS_FATAL("Should not be called"); } diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index dcf5c5b46478..cddc337d95b8 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -111,6 +111,17 @@ flag { } flag { + name: "enable_ni_supl_message_injection_by_carrier_config_bugfix" + namespace: "location" + description: "Flag for enabling NI SUPL message injection by carrier config" + bug: "242105192" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_ni_supl_message_injection_by_carrier_config" namespace: "location" description: "Flag for enabling NI SUPL message injection by carrier config" diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e2e7a46a0ad0..cdb517b3fd3e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6318,7 +6318,14 @@ public class AudioManager { /** * @hide * Get the audio devices that would be used for the routing of the given audio attributes. - * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @param attributes the {@link AudioAttributes} for which the routing is being queried. + * For queries about output devices (playback use cases), a valid usage must be specified in + * the audio attributes via AudioAttributes.Builder.setUsage(). The capture preset MUST NOT + * be changed from default. + * For queries about input devices (capture use case), a valid capture preset MUST be + * specified in the audio attributes via AudioAttributes.Builder.setCapturePreset(). If a + * capture preset is present, then this has precedence over any usage or content type also + * present in the audio attrirutes. * @return an empty list if there was an issue with the request, a list of audio devices * otherwise (typically one device, except for duplicated paths). */ diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index 3cc0ad27bd9a..31f89960836b 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -90,24 +90,24 @@ public final class MediaProjection { mDisplayManager = displayManager; } - /** - * Register a listener to receive notifications about when the {@link MediaProjection} or captured - * content changes state. - * - * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int, - * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications on - * the callback are not missed. The client must implement {@link Callback#onStop()} and clean up - * any resources it is holding, e.g. the {@link VirtualDisplay} and {@link Surface}. This should - * also update any application UI indicating the MediaProjection status as MediaProjection has - * stopped. - * - * @param callback The callback to call. - * @param handler The handler on which the callback should be invoked, or null if the callback - * should be invoked on the calling thread's looper. - * @throws NullPointerException If the given callback is null. - * @see #unregisterCallback - */ - public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) { + /** + * Register a listener to receive notifications about when the {@link MediaProjection} or + * captured content changes state. + * + * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int, + * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications + * on the callback are not missed. The client must implement {@link Callback#onStop()} to + * properly handle MediaProjection clean up any resources it is holding, e.g. the {@link + * VirtualDisplay} and {@link Surface}. This should also update any application UI indicating + * the MediaProjection status as MediaProjection has stopped. + * + * @param callback The callback to call. + * @param handler The handler on which the callback should be invoked, or null if the callback + * should be invoked on the calling thread's looper. + * @throws NullPointerException If the given callback is null. + * @see #unregisterCallback + */ + public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) { try { final Callback c = Objects.requireNonNull(callback); if (handler == null) { @@ -313,7 +313,7 @@ public final class MediaProjection { */ public abstract static class Callback { /** - * Called when the MediaProjection session is no longer valid. + * Called when the MediaProjection session has been stopped and is no longer valid. * * <p>Once a MediaProjection has been stopped, it's up to the application to release any * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and {@link @@ -321,9 +321,9 @@ public final class MediaProjection { * it should be updated to indicate that MediaProjection is no longer active. * * <p>MediaProjection stopping can be a result of the system stopping the ongoing - * MediaProjection due to various reasons, such as another MediaProjection session starting. - * MediaProjection may also stop due to the user explicitly stopping ongoing MediaProjection - * via any available system-level UI. + * MediaProjection due to various reasons, such as another MediaProjection session starting, + * a user stopping the session via UI affordances in system-level UI, or the screen being + * locked. * * <p>After this callback any call to {@link MediaProjection#createVirtualDisplay} will * fail, even if no such {@link VirtualDisplay} was ever created for this MediaProjection diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 03fd2c67fe46..dc55e41fb74c 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -60,13 +60,14 @@ import java.util.Map; * <li>Register a {@link MediaProjection.Callback} by calling {@link * MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to * receive notifications about when the {@link MediaProjection} or captured content changes - * state. When receiving an `onStop()` callback, the client must clean up any resources it is - * holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may - * further no longer create any new {@link VirtualDisplay}s via {@link - * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface, - * VirtualDisplay.Callback, Handler)}. Note that the `onStop()` callback can be a result of - * the system stopping MediaProjection due to various reasons or the user stopping the - * MediaProjection via UI affordances in system-level UI. + * state. When receiving an `onStop()` callback the {@link MediaProjection} session has been + * finished and the client must clean up any resources it is holding, e.g. the {@link + * VirtualDisplay} and {@link Surface}. The MediaProjection may further no longer create any + * new {@link VirtualDisplay}s via {@link MediaProjection#createVirtualDisplay(String, int, + * int, int, int, Surface, VirtualDisplay.Callback, Handler)}. Note that the `onStop()` + * callback can be a result of the system stopping MediaProjection due to various reasons. + * This includes the user stopping the MediaProjection via UI affordances in system-level UI, + * the screen being locked, or another {@link MediaProjection} session starting. * <li>Start the screen capture session for media projection by calling {@link * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface, * android.hardware.display.VirtualDisplay.Callback, Handler)}. diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 25c063d6ccd8..202535d45191 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -273,6 +273,7 @@ LIBANDROID { ASurfaceTransaction_fromJava; # introduced=34 ASurfaceTransaction_reparent; # introduced=29 ASurfaceTransaction_setBuffer; # introduced=29 + ASurfaceTransaction_setBufferWithRelease; # introduced=36 ASurfaceTransaction_setBufferAlpha; # introduced=29 ASurfaceTransaction_setBufferDataSpace; # introduced=29 ASurfaceTransaction_setBufferTransparency; # introduced=29 diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 6ce83cd7b765..e46db6bb3727 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -416,6 +416,35 @@ void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, transaction->setBuffer(surfaceControl, graphic_buffer, fence); } +void ASurfaceTransaction_setBufferWithRelease( + ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, + AHardwareBuffer* buffer, int acquire_fence_fd, void* _Null_unspecified context, + ASurfaceTransaction_OnBufferRelease aReleaseCallback) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + CHECK_NOT_NULL(aReleaseCallback); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + sp<GraphicBuffer> graphic_buffer(GraphicBuffer::fromAHardwareBuffer(buffer)); + + std::optional<sp<Fence>> fence = std::nullopt; + if (acquire_fence_fd != -1) { + fence = new Fence(acquire_fence_fd); + } + + ReleaseBufferCallback releaseBufferCallback = + [context, + aReleaseCallback](const ReleaseCallbackId&, const sp<Fence>& releaseFence, + std::optional<uint32_t> /* currentMaxAcquiredBufferCount */) { + (*aReleaseCallback)(context, (releaseFence) ? releaseFence->dup() : -1); + }; + + transaction->setBuffer(surfaceControl, graphic_buffer, fence, /* frameNumber */ std::nullopt, + /* producerId */ 0, releaseBufferCallback); +} + void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, const ARect& source, const ARect& destination, int32_t transform) { diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index feee89a51e7c..34e33c0df8f5 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1409,6 +1409,8 @@ <string name="media_transfer_this_device_name">This phone</string> <!-- Name of the tablet device. [CHAR LIMIT=30] --> <string name="media_transfer_this_device_name_tablet">This tablet</string> + <!-- Name of the internal speaker. [CHAR LIMIT=30] --> + <string name="media_transfer_this_device_name_desktop">This computer (internal)</string> <!-- Name of the default media output of the TV. [CHAR LIMIT=30] --> <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string> <!-- Name of the internal mic. [CHAR LIMIT=30] --> @@ -1637,7 +1639,13 @@ <string name="cached_apps_freezer_reboot_dialog_text">Your device must be rebooted for this change to apply. Reboot now or cancel.</string> <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] --> - <string name="media_transfer_wired_usb_device_name">Wired headphone</string> + <string name="media_transfer_wired_headphone_name">Wired headphone</string> + + <!-- Name of the 3.5mm headphone, used in desktop devices. [CHAR LIMIT=50] --> + <string name="media_transfer_headphone_name">Headphone</string> + + <!-- Name of the usb audio device speaker, used in desktop devices. [CHAR LIMIT=50] --> + <string name="media_transfer_usb_speaker_name">USB speaker</string> <!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] --> <string name="media_transfer_wired_device_mic_name">Mic jack</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java index 148e164e4806..c9f9d1be9c15 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -151,7 +151,19 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); return; } - mService.addSource(sink, metadata, isGroupOp); + try { + mService.addSource(sink, metadata, isGroupOp); + } catch (IllegalStateException e) { + // BT will check callback registration before add source. + // If it throw callback exception when bt is disabled, then the failure is intended, + // just catch it here. + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null && adapter.isEnabled()) { + throw e; + } else { + Log.d(TAG, "Catch addSource failure when bt is disabled: " + e); + } + } } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java index 548eb3fd4b8f..874e03012ae2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java @@ -57,7 +57,7 @@ public final class InputRouteManager { } }; - /* package */ InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) { + public InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) { mContext = context; mAudioManager = audioManager; Handler handler = new Handler(context.getMainLooper()); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 9eaf8d3838d8..0b8fb22cef3a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -72,6 +72,8 @@ public class PhoneMediaDevice extends MediaDevice { return context.getString(R.string.media_transfer_this_device_name_tv); } else if (isTablet()) { return context.getString(R.string.media_transfer_this_device_name_tablet); + } else if (inputRoutingEnabledAndIsDesktop()) { + return context.getString(R.string.media_transfer_this_device_name_desktop); } else { return context.getString(R.string.media_transfer_this_device_name); } @@ -85,10 +87,18 @@ public class PhoneMediaDevice extends MediaDevice { switch (routeInfo.getType()) { case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: + name = + inputRoutingEnabledAndIsDesktop() + ? context.getString(R.string.media_transfer_headphone_name) + : context.getString(R.string.media_transfer_wired_headphone_name); + break; case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_ACCESSORY: - name = context.getString(R.string.media_transfer_wired_usb_device_name); + name = + inputRoutingEnabledAndIsDesktop() + ? context.getString(R.string.media_transfer_usb_speaker_name) + : context.getString(R.string.media_transfer_wired_headphone_name); break; case TYPE_DOCK: name = context.getString(R.string.media_transfer_dock_speaker_device_name); @@ -139,6 +149,16 @@ public class PhoneMediaDevice extends MediaDevice { .contains("tablet"); } + static boolean isDesktop() { + return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(",")) + .contains("desktop"); + } + + static boolean inputRoutingEnabledAndIsDesktop() { + return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl() + && isDesktop(); + } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. @SuppressWarnings("NewApi") @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index e2d58d660fd5..da5f428ce23b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -47,6 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowSystemProperties; @RunWith(RobolectricTestRunner.class) public class PhoneMediaDeviceTest { @@ -105,12 +106,37 @@ public class PhoneMediaDeviceTest { when(mInfo.getName()).thenReturn(deviceName); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name)); when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name)); + + when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); + + assertThat(mPhoneMediaDevice.getName()).isEqualTo(getMediaTransferThisDeviceName(mContext)); + } + + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) + @Test + public void getName_returnCorrectName_desktop() { + ShadowSystemProperties.override("ro.build.characteristics", "desktop"); + + when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES); + + assertThat(mPhoneMediaDevice.getName()) + .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name)); + + when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET); + + assertThat(mPhoneMediaDevice.getName()) + .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name)); + + when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE); + + assertThat(mPhoneMediaDevice.getName()) + .isEqualTo(mContext.getString(R.string.media_transfer_usb_speaker_name)); when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index afbe84c54d9d..fbce6ca07b3e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -261,22 +261,13 @@ public final class DeviceConfigService extends Binder { public static HashMap<String, String> getAllFlags(IContentProvider provider) { HashMap<String, String> allFlags = new HashMap<String, String>(); - try { - Bundle args = new Bundle(); - args.putInt(Settings.CALL_METHOD_USER_KEY, - ActivityManager.getService().getCurrentUser().id); - Bundle b = provider.call(new AttributionSource(Process.myUid(), - resolveCallingPackage(), null), Settings.AUTHORITY, - Settings.CALL_METHOD_LIST_CONFIG, null, args); - if (b != null) { - Map<String, String> flagsToValues = - (HashMap) b.getSerializable(Settings.NameValueTable.VALUE); - allFlags.putAll(flagsToValues); + for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) { + List<String> keys = new ArrayList<>(properties.getKeyset()); + for (String flagName : properties.getKeyset()) { + String fullName = properties.getNamespace() + "/" + flagName; + allFlags.put(fullName, properties.getString(flagName, null)); } - } catch (RemoteException e) { - throw new RuntimeException("Failed in IPC", e); } - return allFlags; } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index ba59ce81d362..0ae4da53045e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -545,6 +545,10 @@ public class SettingsProvider extends ContentProvider { reportDeviceConfigAccess(prefix); return result; } + case Settings.CALL_METHOD_LIST_NAMESPACES_CONFIG -> { + Bundle result = packageNamespacesForCallResult(getAllConfigFlagNamespaces()); + return result; + } case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> { RemoteCallback callback = args.getParcelable( Settings.CALL_METHOD_MONITOR_CALLBACK_KEY); @@ -1337,6 +1341,23 @@ public class SettingsProvider extends ContentProvider { } @NonNull + private HashSet<String> getAllConfigFlagNamespaces() { + Set<String> flagNames = getAllConfigFlags(null).keySet(); + HashSet<String> namespaces = new HashSet(); + for (String name : flagNames) { + int slashIndex = name.indexOf("/"); + boolean validSlashIndex = slashIndex != -1 + && slashIndex != 0 + && slashIndex != name.length(); + if (validSlashIndex) { + String namespace = name.substring(0, slashIndex); + namespaces.add(namespace); + } + } + return namespaces; + } + + @NonNull private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) { if (DEBUG) { Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix); @@ -2561,6 +2582,12 @@ public class SettingsProvider extends ContentProvider { return result; } + private Bundle packageNamespacesForCallResult(@NonNull HashSet<String> namespaces) { + Bundle result = new Bundle(); + result.putSerializable(Settings.NameValueTable.VALUE, namespaces); + return result; + } + private void setMonitorCallback(RemoteCallback callback) { if (callback == null) { return; diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index f59eab001be9..cd16af76d4b8 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -452,7 +452,7 @@ filegroup { "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java", "tests/src/**/systemui/doze/DozeScreenStateTest.java", "tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java", - "tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java", + "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java", "tests/src/**/systemui/navigationbar/views/NavigationBarTest.java", "tests/src/**/systemui/power/PowerNotificationWarningsTest.java", "tests/src/**/systemui/power/PowerUITest.java", diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 7974f9222a0c..855ebe3799b8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -149,6 +149,16 @@ flag { } flag { + name: "modes_dialog_single_rows" + namespace: "systemui" + description: "[Experiment] Display one entry per grid row in the Modes Dialog." + bug: "366034002" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" @@ -528,6 +538,13 @@ flag { } flag { + name: "status_bar_connected_displays" + namespace: "systemui" + description: "Shows the status bar on connected displays" + bug: "362720336" +} + +flag { name: "status_bar_switch_to_spn_from_data_spn" namespace: "systemui" description: "Fix usage of the SPN broadcast extras" @@ -538,10 +555,10 @@ flag { } flag { - name: "haptic_volume_slider" + name: "status_bar_simple_fragment" namespace: "systemui" - description: "Adds haptic feedback to the volume slider." - bug: "316953430" + description: "Feature flag for refactoring the collapsed status bar fragment" + bug: "364360986" } flag { @@ -668,13 +685,6 @@ flag { } flag { - name: "compose_lockscreen" - namespace: "systemui" - description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass." - bug: "301968149" -} - -flag { name: "enable_contextual_tip_for_power_off" namespace: "systemui" description: "Enables on-screen contextual tip about how to power off or restart phone" diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt index c01396a96b6e..4674d6e5f25a 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt @@ -16,16 +16,15 @@ package com.android.compose.windowsizeclass -import android.view.WindowManager import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.window.layout.WindowMetricsCalculator val LocalWindowSizeClass = staticCompositionLocalOf<WindowSizeClass> { @@ -42,10 +41,7 @@ fun calculateWindowSizeClass(): WindowSizeClass { LocalConfiguration.current val density = LocalDensity.current val context = LocalContext.current - val metrics = - remember(context) { - context.getSystemService(WindowManager::class.java)!!.currentWindowMetrics - } + val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() } return WindowSizeClass.calculateFromSize(size) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 163b35596c1b..8321238b28b1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -16,7 +16,6 @@ package com.android.systemui.bouncer.ui.composable -import android.view.HapticFeedbackConstants import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D @@ -133,10 +132,7 @@ fun PatternBouncer( // Perform haptic feedback, but only if the current dot is not null, so we don't perform it // when the UI first shows up or when the user lifts their pointer/finger. if (currentDot != null) { - view.performHapticFeedback( - HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, - ) + viewModel.performDotFeedback(view) } if (!isAnimationEnabled) { @@ -206,10 +202,7 @@ fun PatternBouncer( // Show the failure animation if the user entered the wrong input. LaunchedEffect(animateFailure) { if (animateFailure) { - showFailureAnimation( - dots = dots, - scalingAnimatables = dotScalingAnimatables, - ) + showFailureAnimation(dots = dots, scalingAnimatables = dotScalingAnimatables) viewModel.onFailureAnimationShown() } } @@ -358,15 +351,10 @@ fun PatternBouncer( (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset drawCircle( center = - pixelOffset( - dot, - spacing, - horizontalOffset, - verticalOffset + appearOffset, - ), + pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset), color = dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value), - radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value + radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value, ) } } @@ -387,7 +375,7 @@ private suspend fun showEntryAnimation( delayMillis = 33 * dot.y, durationMillis = 450, easing = Easings.LegacyDecelerate, - ) + ), ) } } @@ -400,7 +388,7 @@ private suspend fun showEntryAnimation( delayMillis = 0, durationMillis = 450 + (33 * dot.y), easing = Easings.StandardDecelerate, - ) + ), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 489e24e8b328..0830c9b359a4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -16,8 +16,8 @@ package com.android.systemui.bouncer.ui.composable -import android.view.HapticFeedbackConstants import android.view.MotionEvent +import android.view.View import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec @@ -72,11 +72,7 @@ import kotlinx.coroutines.launch /** Renders the PIN button pad. */ @Composable -fun PinPad( - viewModel: PinBouncerViewModel, - verticalSpacing: Dp, - modifier: Modifier = Modifier, -) { +fun PinPad(viewModel: PinBouncerViewModel, verticalSpacing: Dp, modifier: Modifier = Modifier) { DisposableEffect(Unit) { onDispose { viewModel.onHidden() } } val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() @@ -104,7 +100,7 @@ fun PinPad( columns = columns, verticalSpacing = verticalSpacing, horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp), - modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid") + modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid"), ) { repeat(9) { index -> DigitButton( @@ -126,10 +122,11 @@ fun PinPad( ), isInputEnabled = isInputEnabled, onClicked = viewModel::onBackspaceButtonClicked, + onPointerDown = viewModel::onBackspaceButtonPressed, onLongPressed = viewModel::onBackspaceButtonLongPressed, appearance = backspaceButtonAppearance, scaling = buttonScaleAnimatables[9]::value, - elementId = "delete_button" + elementId = "delete_button", ) DigitButton( @@ -138,7 +135,7 @@ fun PinPad( onClicked = viewModel::onPinButtonClicked, scaling = buttonScaleAnimatables[10]::value, isAnimationEnabled = isDigitButtonAnimationEnabled, - onPointerDown = viewModel::onDigitButtonDown + onPointerDown = viewModel::onDigitButtonDown, ) ActionButton( @@ -152,7 +149,7 @@ fun PinPad( onClicked = viewModel::onAuthenticateButtonClicked, appearance = confirmButtonAppearance, scaling = buttonScaleAnimatables[11]::value, - elementId = "key_enter" + elementId = "key_enter", ) } } @@ -162,7 +159,7 @@ private fun DigitButton( digit: Int, isInputEnabled: Boolean, onClicked: (Int) -> Unit, - onPointerDown: () -> Unit, + onPointerDown: (View?) -> Unit, scaling: () -> Float, isAnimationEnabled: Boolean, ) { @@ -178,7 +175,7 @@ private fun DigitButton( val scale = if (isAnimationEnabled) scaling() else 1f scaleX = scale scaleY = scale - } + }, ) { contentColor -> // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes // it into Text, use that here, to animate more efficiently. @@ -197,6 +194,7 @@ private fun ActionButton( onClicked: () -> Unit, elementId: String, onLongPressed: (() -> Unit)? = null, + onPointerDown: ((View?) -> Unit)? = null, appearance: ActionButtonAppearance, scaling: () -> Float, ) { @@ -222,18 +220,16 @@ private fun ActionButton( foregroundColor = foregroundColor, isAnimationEnabled = true, elementId = elementId, + onPointerDown = onPointerDown, modifier = Modifier.graphicsLayer { alpha = hiddenAlpha val scale = scaling() scaleX = scale scaleY = scale - } + }, ) { contentColor -> - Icon( - icon = icon, - tint = contentColor(), - ) + Icon(icon = icon, tint = contentColor()) } } @@ -247,22 +243,13 @@ private fun PinPadButton( modifier: Modifier = Modifier, elementId: String? = null, onLongPressed: (() -> Unit)? = null, - onPointerDown: (() -> Unit)? = null, + onPointerDown: ((View?) -> Unit)? = null, content: @Composable (contentColor: () -> Color) -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val indication = LocalIndication.current.takeUnless { isPressed } - val view = LocalView.current - LaunchedEffect(isPressed) { - if (isPressed) { - view.performHapticFeedback( - HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, - ) - } - } // Pin button animation specification is asymmetric: fast animation to the pressed state, and a // slow animation upon release. Note that isPressed is guaranteed to be true for at least the @@ -277,7 +264,7 @@ private fun PinPadButton( animateDpAsState( if (isAnimationEnabled && isPressed) 24.dp else pinButtonMaxSize / 2, label = "PinButton round corners", - animationSpec = tween(animDurationMillis, easing = animEasing) + animationSpec = tween(animDurationMillis, easing = animEasing), ) val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing) val containerColor: Color by @@ -287,7 +274,7 @@ private fun PinPadButton( else -> backgroundColor }, label = "Pin button container color", - animationSpec = colorAnimationSpec + animationSpec = colorAnimationSpec, ) val contentColor = animateColorAsState( @@ -296,7 +283,7 @@ private fun PinPadButton( else -> foregroundColor }, label = "Pin button container color", - animationSpec = colorAnimationSpec + animationSpec = colorAnimationSpec, ) Box( @@ -319,11 +306,11 @@ private fun PinPadButton( interactionSource = interactionSource, indication = indication, onClick = onClicked, - onLongClick = onLongPressed + onLongClick = onLongPressed, ) .pointerInteropFilter { motionEvent -> if (motionEvent.action == MotionEvent.ACTION_DOWN) { - onPointerDown?.let { it() } + onPointerDown?.let { it(view) } } false } @@ -353,10 +340,7 @@ private suspend fun showFailureAnimation( animatable.animateTo( targetValue = 1f, animationSpec = - tween( - durationMillis = pinButtonErrorRevertMs, - easing = Easings.Legacy, - ), + tween(durationMillis = pinButtonErrorRevertMs, easing = Easings.Legacy), ) } } @@ -364,9 +348,7 @@ private suspend fun showFailureAnimation( } /** Returns the amount of horizontal spacing between columns, in dips. */ -private fun calculateHorizontalSpacingBetweenColumns( - gridWidth: Dp, -): Dp { +private fun calculateHorizontalSpacingBetweenColumns(gridWidth: Dp): Dp { return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt index 296fc27ac0ff..dcf32b2bcda4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt @@ -16,15 +16,10 @@ package com.android.systemui.common.ui.compose.windowinsets -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -36,9 +31,6 @@ val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() } /** The corner radius in px of the current display. */ val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp } -/** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */ -val LocalRawScreenHeight = staticCompositionLocalOf { 0f } - @Composable fun ScreenDecorProvider( displayCutout: StateFlow<DisplayCutout>, @@ -48,22 +40,9 @@ fun ScreenDecorProvider( val cutout by displayCutout.collectAsStateWithLifecycle() val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() } - val density = LocalDensity.current - val navBarHeight = - with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() } - val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding() - val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding() - val screenHeight = - with(density) { - (LocalConfiguration.current.screenHeightDp.dp + - maxOf(statusBarHeight, displayCutoutHeight)) - .toPx() - } + navBarHeight - CompositionLocalProvider( LocalScreenCornerRadius provides screenCornerRadiusDp, LocalDisplayCutout provides cutout, - LocalRawScreenHeight provides screenHeight, ) { content() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index 5f7b1adca6a0..f4d9e820ad8f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -35,7 +35,6 @@ import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.res.R import com.android.systemui.util.animation.MeasurementInput -import kotlinx.coroutines.ExperimentalCoroutinesApi object MediaCarousel { object Elements { @@ -47,7 +46,6 @@ object MediaCarousel { } } -@ExperimentalCoroutinesApi @Composable fun SceneScope.MediaCarousel( isVisible: Boolean, @@ -79,7 +77,7 @@ fun SceneScope.MediaCarousel( offsetProvider?.invoke() ?: IntOffset.Zero ) } - } + }, ) .layout { measurable, constraints -> val placeable = measurable.measure(constraints) @@ -89,7 +87,7 @@ fun SceneScope.MediaCarousel( MeasurementInput(placeable.width, placeable.height) carouselController.setSceneContainerSize( placeable.width, - placeable.height + placeable.height, ) layout(placeable.width, placeable.height) { @@ -106,7 +104,7 @@ fun SceneScope.MediaCarousel( } }, update = { it.setView(carouselController.mediaFrame) }, - onRelease = { it.removeAllViews() } + onRelease = { it.removeAllViews() }, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt index 897a8613263f..a2ae8bbf66e4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt @@ -24,9 +24,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp import com.android.compose.nestedscroll.PriorityNestedScrollConnection -import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import kotlin.math.max import kotlin.math.roundToInt import kotlin.math.tanh @@ -36,9 +38,10 @@ import kotlinx.coroutines.launch @Composable fun Modifier.stackVerticalOverscroll( coroutineScope: CoroutineScope, - canScrollForward: () -> Boolean + canScrollForward: () -> Boolean, ): Modifier { - val screenHeight = LocalRawScreenHeight.current + val screenHeight = + with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } val overscrollOffset = remember { Animatable(0f) } val stackNestedScrollConnection = remember { NotificationStackNestedScrollConnection( @@ -60,10 +63,10 @@ fun Modifier.stackVerticalOverscroll( overscrollOffset.animateTo( targetValue = 0f, initialVelocity = velocityAvailable, - animationSpec = tween() + animationSpec = tween(), ) } - } + }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 91ecfc18a76e..1b99a9644575 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable import android.util.Log import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background @@ -29,6 +30,8 @@ import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.absoluteOffset @@ -36,9 +39,11 @@ import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme @@ -68,6 +73,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp @@ -81,7 +87,6 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf -import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession @@ -96,6 +101,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import kotlin.math.roundToInt +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch object Notifications { @@ -171,7 +177,7 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( setCurrent = { scrollOffset = it }, min = minScrollOffset, max = maxScrollOffset, - delta + delta, ) } @@ -209,8 +215,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( calculateHeadsUpPlaceholderYOffset( scrollOffset.roundToInt(), minScrollOffset.roundToInt(), - stackScrollView.topHeadsUpHeight - ) + stackScrollView.topHeadsUpHeight, + ), ) } .thenIf(isHeadsUp) { @@ -218,11 +224,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( bottomBehavior = NestedScrollBehavior.EdgeAlways ) .nestedScroll(nestedScrollConnection) - .scrollable( - orientation = Orientation.Vertical, - state = scrollableState, - ) - } + .scrollable(orientation = Orientation.Vertical, state = scrollableState) + }, ) } @@ -259,6 +262,7 @@ fun SceneScope.ConstrainedNotificationStack( * Adds the space where notification stack should appear in the scene, with a scrim and nested * scrolling. */ +@OptIn(ExperimentalLayoutApi::class) @Composable fun SceneScope.NotificationScrollingStack( shadeSession: SaveableSession, @@ -291,7 +295,7 @@ fun SceneScope.NotificationScrollingStack( val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp - val screenHeight = LocalRawScreenHeight.current + val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } /** * The height in px of the contents of notification stack. Depending on the number of @@ -325,6 +329,14 @@ fun SceneScope.NotificationScrollingStack( screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() } } + val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false) + + // The bottom Y bound of the currently focused remote input notification. + val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f) + + // The top y bound of the IME. + val imeTop = remember { mutableFloatStateOf(0f) } + // we are not scrolled to the top unless the scrim is at its maximum offset. LaunchedEffect(viewModel, scrimOffset) { snapshotFlow { scrimOffset.value >= 0f } @@ -342,15 +354,34 @@ fun SceneScope.NotificationScrollingStack( LaunchedEffect(syntheticScroll, scrimOffset, scrollState) { snapshotFlow { syntheticScroll.value } .collect { delta -> - val minOffset = minScrimOffset() - if (scrimOffset.value > minOffset) { - val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f) - scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset)) - if (remainingDelta > 0f) { - scrollState.scrollBy(remainingDelta) - } - } else { - scrollState.scrollTo(delta.roundToInt()) + scrollNotificationStack( + scope = coroutineScope, + delta = delta, + animate = false, + scrimOffset = scrimOffset, + minScrimOffset = minScrimOffset, + scrollState = scrollState, + ) + } + } + + // if remote input state changes, compare the row and IME's overlap and offset the scrim and + // placeholder accordingly. + LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) { + imeTop.floatValue = 0f + snapshotFlow { imeTop.floatValue } + .collect { imeTopValue -> + // only scroll the stack if ime value has been populated (ime placeholder has been + // composed at least once), and our remote input row overlaps with the ime bounds. + if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) { + scrollNotificationStack( + scope = coroutineScope, + delta = remoteInputRowBottom - imeTopValue, + animate = true, + scrimOffset = scrimOffset, + minScrimOffset = minScrimOffset, + scrollState = scrollState, + ) } } } @@ -394,12 +425,12 @@ fun SceneScope.NotificationScrollingStack( scrimOffset.value < 0 && layoutState.isTransitioning( from = Scenes.Shade, - to = Scenes.QuickSettings + to = Scenes.QuickSettings, ) ) { IntOffset( x = 0, - y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt() + y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(), ) } else { IntOffset(x = 0, y = scrimOffset.value.roundToInt()) @@ -458,13 +489,11 @@ fun SceneScope.NotificationScrollingStack( .thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() } .debugBackground(viewModel, DEBUG_BOX_COLOR) ) { - NotificationPlaceholder( - stackScrollView = stackScrollView, - viewModel = viewModel, + Column( modifier = Modifier.verticalNestedScrollToScene( topBehavior = NestedScrollBehavior.EdgeWithPreview, - isExternalOverscrollGesture = { isCurrentGestureOverscroll.value } + isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }, ) .thenIf(shadeMode == ShadeMode.Single) { Modifier.nestedScroll(scrimNestedScrollConnection) @@ -473,18 +502,31 @@ fun SceneScope.NotificationScrollingStack( .verticalScroll(scrollState) .padding(top = topPadding) .fillMaxWidth() - .notificationStackHeight( - view = stackScrollView, - totalVerticalPadding = topPadding + bottomPadding, - ) - .onSizeChanged { size -> stackHeight.intValue = size.height }, - ) + ) { + NotificationPlaceholder( + stackScrollView = stackScrollView, + viewModel = viewModel, + modifier = + Modifier.notificationStackHeight( + view = stackScrollView, + totalVerticalPadding = topPadding + bottomPadding, + ) + .onSizeChanged { size -> stackHeight.intValue = size.height }, + ) + Spacer( + modifier = + Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget) + .onGloballyPositioned { coordinates: LayoutCoordinates -> + imeTop.floatValue = screenHeight - coordinates.size.height + } + ) + } } if (shouldIncludeHeadsUpSpace) { HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, - modifier = Modifier.padding(top = topPadding) + modifier = Modifier.padding(top = topPadding), ) } } @@ -572,6 +614,42 @@ private fun SceneScope.NotificationPlaceholder( ) } +private suspend fun scrollNotificationStack( + scope: CoroutineScope, + delta: Float, + animate: Boolean, + scrimOffset: Animatable<Float, AnimationVector1D>, + minScrimOffset: () -> Float, + scrollState: ScrollState, +) { + val minOffset = minScrimOffset() + if (scrimOffset.value > minOffset) { + val remainingDelta = + (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt() + if (remainingDelta > 0) { + if (animate) { + // launch a new coroutine for the remainder animation so that it doesn't suspend the + // scrim animation, allowing both to play simultaneously. + scope.launch { scrollState.animateScrollTo(remainingDelta) } + } else { + scrollState.scrollTo(remainingDelta) + } + } + val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset) + if (animate) { + scrimOffset.animateTo(newScrimOffset) + } else { + scrimOffset.snapTo(newScrimOffset) + } + } else { + if (animate) { + scrollState.animateScrollBy(delta) + } else { + scrollState.scrollBy(delta) + } + } +} + private fun calculateCornerRadius( scrimCornerRadius: Dp, screenCornerRadius: Dp, @@ -618,7 +696,7 @@ private fun consumeDeltaWithinRange( setCurrent: (Float) -> Unit, min: Float, max: Float, - delta: Float + delta: Float, ): Float { return if (delta < 0 && current > min) { val remainder = (current + delta - min).coerceAtMost(0f) @@ -631,10 +709,7 @@ private fun consumeDeltaWithinRange( } else 0f } -private inline fun debugLog( - viewModel: NotificationsPlaceholderViewModel, - msg: () -> Any, -) { +private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) { if (viewModel.isDebugLoggingEnabled) { Log.d(TAG, msg().toString()) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index fa92bef34f38..d91958adaa1b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource @@ -79,7 +80,6 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout -import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.ExclusiveActivatable @@ -229,17 +229,16 @@ private fun SceneScope.QuickSettingsScene( } .thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() } ) { + val density = LocalDensity.current val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() val customizingAnimationDuration by viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() - val screenHeight = LocalRawScreenHeight.current + val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() } - val collapsedHeaderHeight = - with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } val lifecycleOwner = LocalLifecycleOwner.current val footerActionsViewModel = remember(lifecycleOwner, viewModel) { @@ -268,7 +267,6 @@ private fun SceneScope.QuickSettingsScene( val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() - val density = LocalDensity.current val bottomPadding by animateDpAsState( targetValue = if (isCustomizing) 0.dp else navBarBottomHeight, @@ -394,7 +392,7 @@ private fun SceneScope.QuickSettingsScene( { mediaOffset.roundToPx() }, ) } - Box(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) { + Column(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) { if (mediaInRow) { Layout(content = content, measurePolicy = landscapeQsMediaMeasurePolicy) } else { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index b85523bc1694..6c4edf49fd83 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade.ui.composable +import android.view.HapticFeedbackConstants import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -39,17 +40,20 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.systemui.scene.shared.model.Scenes /** Renders a lightweight shade UI container, as an overlay. */ @Composable @@ -58,6 +62,13 @@ fun SceneScope.OverlayShade( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { + val view = LocalView.current + LaunchedEffect(Unit) { + if (layoutState.currentTransition?.fromContent == Scenes.Gone) { + view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START) + } + } + Box(modifier) { Scrim(onClicked = onScrimClicked) diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md index 0ac15c583b29..234c7a032d2e 100644 --- a/packages/SystemUI/docs/scene.md +++ b/packages/SystemUI/docs/scene.md @@ -68,15 +68,13 @@ file evalutes to `true`. 1. Set a collection of **aconfig flags** to `true` by running the following commands: ```console - $ adb shell device_config override systemui com.android.systemui.scene_container true - $ adb shell device_config override systemui com.android.systemui.compose_lockscreen true $ adb shell device_config override systemui com.android.systemui.keyguard_bottom_area_refactor true $ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true - $ adb shell device_config override systemui com.android.systemui.media_in_scene_container true $ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true - $ adb shell device_config override systemui com.android.systemui.notifications_heads_up_refactor true + $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true $ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true $ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true + $ adb shell device_config override systemui com.android.systemui.scene_container true ``` 2. **Restart** System UI by issuing the following command: ```console diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index deef65218c4b..9552564cf1a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -16,18 +16,26 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.haptics.msdl.bouncerHapticPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.testKosmos +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -39,11 +47,15 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope + private val msdlPlayer = kosmos.fakeMSDLPlayer + private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer + private val authInteractionProperties = AuthInteractionProperties() private val underTest = kosmos.pinBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Pin, + bouncerHapticPlayer = bouncerHapticPlayer, ) @Before @@ -77,4 +89,42 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(animateFailure).isFalse() } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onAuthenticationResult_playUnlockTokenIfSuccessful() = + testScope.runTest { + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + // Correct PIN: + FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> + underTest.onPinButtonClicked(digit) + } + underTest.onAuthenticateButtonClicked() + runCurrent() + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onAuthenticationResult_playFailureTokenIfFailure() = + testScope.runTest { + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + // Wrong PIN: + FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit -> + underTest.onPinButtonClicked(digit) + } + underTest.onAuthenticateButtonClicked() + runCurrent() + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 7c773a902367..c163c6fc0a30 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -16,9 +16,11 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.authenticationRepository @@ -27,12 +29,16 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.haptics.msdl.FakeMSDLPlayer +import com.android.systemui.haptics.msdl.bouncerHapticPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -55,10 +61,13 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel } + private val msdlPlayer: FakeMSDLPlayer = kosmos.fakeMSDLPlayer + private val bouncerHapticHelper = kosmos.bouncerHapticPlayer private val underTest = kosmos.patternBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true).asStateFlow(), onIntentionalUserInput = {}, + bouncerHapticPlayer = bouncerHapticHelper, ) private val containerSize = 90 // px @@ -115,10 +124,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { .that(selectedDots) .isEqualTo( CORRECT_PATTERN.subList(0, index + 1).map { - PatternDotViewModel( - x = it.x, - y = it.y, - ) + PatternDotViewModel(x = it.x, y = it.y) } ) assertWithMessage("Wrong current dot for index $index") @@ -174,7 +180,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { listOf( PatternDotViewModel(0, 0), PatternDotViewModel(1, 0), - PatternDotViewModel(2, 0) + PatternDotViewModel(2, 0), ) ) } @@ -200,7 +206,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { listOf( PatternDotViewModel(1, 0), PatternDotViewModel(1, 1), - PatternDotViewModel(1, 2) + PatternDotViewModel(1, 2), ) ) } @@ -228,7 +234,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { listOf( PatternDotViewModel(2, 0), PatternDotViewModel(1, 1), - PatternDotViewModel(0, 2) + PatternDotViewModel(0, 2), ) ) } @@ -300,10 +306,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1 repeat(attempts) { attempt -> underTest.onDragStart() - CORRECT_PATTERN.subList( - 0, - kosmos.authenticationRepository.minPatternLength - 1, - ) + CORRECT_PATTERN.subList(0, kosmos.authenticationRepository.minPatternLength - 1) .forEach { coordinate -> underTest.onDrag( xPx = 30f * coordinate.x + 15, @@ -341,6 +344,16 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(authResult).isTrue() } + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun performDotFeedback_deliversDragToken() = + testScope.runTest { + underTest.performDotFeedback(null) + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + private fun dragOverCoordinates(vararg coordinatesDragged: Point) { underTest.onDragStart() coordinatesDragged.forEach(::dragToCoordinate) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 2ee4aee2abab..af5f2acb444d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.input.key.KeyEventType import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -35,12 +36,15 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository import com.android.systemui.classifier.fakeFalsingCollector import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.haptics.msdl.bouncerHapticPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlin.random.Random import kotlin.random.nextInt @@ -64,11 +68,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } + private val msdlPlayer = kosmos.fakeMSDLPlayer + private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer private val underTest by lazy { kosmos.pinBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Pin, + bouncerHapticPlayer = bouncerHapticPlayer, ) } @@ -97,6 +104,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Sim, + bouncerHapticPlayer = bouncerHapticPlayer, ) assertThat(underTest.isSimAreaVisible).isTrue() @@ -122,6 +130,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Pin, + bouncerHapticPlayer = bouncerHapticPlayer, ) kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) val hintedPinLength by collectLastValue(underTest.hintedPinLength) @@ -487,11 +496,39 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { lockDeviceAndOpenPinBouncer() - underTest.onDigitButtonDown() + underTest.onDigitButtonDown(null) assertTrue(kosmos.fakeFalsingCollector.wasLastGestureAvoided()) } + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onDigiButtonDown_deliversKeyStandardToken() = + testScope.runTest { + underTest.onDigitButtonDown(null) + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_STANDARD) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onBackspaceButtonPressed_deliversKeyDeleteToken() { + underTest.onBackspaceButtonPressed(null) + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_DELETE) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onBackspaceButtonLongPressed_deliversLongPressToken() { + underTest.onBackspaceButtonLongPressed() + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 956c12916c98..2028d28804bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.view.MotionEvent; import android.view.accessibility.AccessibilityManager; @@ -33,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -201,6 +204,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) public void testTrackpadGesture() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true); @@ -208,6 +212,14 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void testTrackpadGesture_touchScreenSource_false() { + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + } + + @Test public void testAddAndRemoveFalsingBeliefListener() { verify(mHistoryTracker, never()).addBeliefListener(any()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index df4b0480f5c7..5a4799cecae5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -312,6 +312,7 @@ public class FalsingDataProviderTest extends ClassifierTest { } @Test + @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) public void test_IsFromTrackpad() { MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index c2acc5ff6689..160865d625f5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -31,8 +31,11 @@ import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.AuthenticationFlags +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest @@ -211,7 +214,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) kosmos.fakeUserRepository.setSelectedUserInfo( primaryUser, - SelectionStatus.SELECTION_COMPLETE + SelectionStatus.SELECTION_COMPLETE, ) kosmos.fakeTrustRepository.setCurrentUserTrusted(true) @@ -240,6 +243,49 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { } @Test + fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = this, + ) + kosmos.powerInteractor.setAsleepForTest() + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + } + + @Test + fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + assertThat(kosmos.keyguardTransitionInteractor.getCurrentState()) + .isEqualTo(KeyguardState.LOCKSCREEN) + + kosmos.powerInteractor.setAsleepForTest() + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + } + + @Test fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() = testScope.runTest { kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) @@ -273,7 +319,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to DeviceEntryRestrictionReason.UserLockdown, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to - DeviceEntryRestrictionReason.PolicyLockdown + DeviceEntryRestrictionReason.PolicyLockdown, ) } @@ -285,7 +331,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - "not mainline reboot" + "not mainline reboot", ) runCurrent() @@ -321,7 +367,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - "not mainline reboot" + "not mainline reboot", ) runCurrent() @@ -358,7 +404,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - "not mainline reboot" + "not mainline reboot", ) runCurrent() @@ -394,12 +440,12 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { collectLastValue(underTest.deviceEntryRestrictionReason) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE + DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE, ) kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags( AuthenticationFlags( userId = 1, - flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT + flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT, ) ) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index 686b518b56e0..366b55db4f20 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.classifier.falsingManager import com.android.systemui.haptics.fakeVibratorHelper import com.android.systemui.kosmos.testScope import com.android.systemui.log.core.FakeLogBuffer @@ -68,11 +69,13 @@ class QSLongPressEffectTest : SysuiTestCase() { vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_SPIN] = spinDuration whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true) + kosmos.falsingManager.setFalseLongTap(false) longPressEffect = QSLongPressEffect( vibratorHelper, kosmos.keyguardStateController, + kosmos.falsingManager, FakeLogBuffer.Factory.create(), ) longPressEffect.callback = callback @@ -180,11 +183,7 @@ class QSLongPressEffectTest : SysuiTestCase() { // THEN the expected texture is played val reverseHaptics = - LongPressHapticBuilder.createReversedEffect( - progress, - lowTickDuration, - effectDuration, - ) + LongPressHapticBuilder.createReversedEffect(progress, lowTickDuration, effectDuration) assertThat(reverseHaptics).isNotNull() assertThat(vibratorHelper.hasVibratedWithEffects(reverseHaptics!!)).isTrue() } @@ -224,6 +223,20 @@ class QSLongPressEffectTest : SysuiTestCase() { } @Test + fun onAnimationComplete_isFalseLongClick_effectEndsInIdleWithReset() = + testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) { + // GIVEN that the long-click is false + kosmos.falsingManager.setFalseLongTap(true) + + // GIVEN that the animation completes + longPressEffect.handleAnimationComplete() + + // THEN the long-press effect ends in the idle state and the properties are reset + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + verify(callback, times(1)).onResetProperties() + } + + @Test fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() = testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) { // GIVEN that the animation completes diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt index 0c716137f434..639737b37efd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD @@ -53,6 +54,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock +import org.mockito.kotlin.mock @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -81,6 +83,7 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() { Optional.of(kosmos.touchpadGesturesInteractor), KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo), hasTouchpadTutorialScreens, + mock<InputDeviceTutorialLogger>(), SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral)) ) lifecycle.addObserver(viewModel) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index 6c3c7ef0162d..fcf4662be145 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -16,7 +16,10 @@ */ package com.android.systemui.keyguard.data.quickaffordance +import android.app.Flags import android.net.Uri +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF @@ -25,6 +28,7 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.EnableZenModeDialog +import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription @@ -35,7 +39,11 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.settings.data.repository.secureSettingsRepository import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository +import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository +import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq @@ -43,6 +51,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat +import java.time.Duration import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -66,8 +75,13 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { private val kosmos = testKosmos() private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope + private val settings = kosmos.fakeSettings + private val zenModeRepository = kosmos.fakeZenModeRepository + private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository + private val secureSettingsRepository = kosmos.secureSettingsRepository + @Mock private lateinit var zenModeController: ZenModeController @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var conditionUri: Uri @@ -85,17 +99,36 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { DoNotDisturbQuickAffordanceConfig( context, zenModeController, + kosmos.zenModeInteractor, settings, userTracker, testDispatcher, + testScope.backgroundScope, conditionUri, enableZenModeDialog, ) } @Test + @EnableFlags(Flags.FLAG_MODES_UI) fun dndNotAvailable_pickerStateHidden() = testScope.runTest { + deviceProvisioningRepository.setDeviceProvisioned(false) + runCurrent() + + val result = underTest.getPickerScreenState() + runCurrent() + + assertEquals( + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, + result, + ) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun controllerDndNotAvailable_pickerStateHidden() = + testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(false) @@ -105,13 +138,33 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { // then assertEquals( KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, - result + result, ) } @Test + @EnableFlags(Flags.FLAG_MODES_UI) fun dndAvailable_pickerStateVisible() = testScope.runTest { + deviceProvisioningRepository.setDeviceProvisioned(true) + runCurrent() + + val result = underTest.getPickerScreenState() + runCurrent() + + assertThat(result) + .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java) + val defaultPickerState = + result as KeyguardQuickAffordanceConfig.PickerScreenState.Default + assertThat(defaultPickerState.configureIntent).isNotNull() + assertThat(defaultPickerState.configureIntent?.action) + .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun controllerDndAvailable_pickerStateVisible() = + testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -129,7 +182,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsNotOff_setToOff() = + testScope.runTest { + val currentModes by collectLastValue(zenModeRepository.modes) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2) + collectLastValue(underTest.lockScreenState) + runCurrent() + + val result = underTest.onTriggered(null) + runCurrent() + + val dndMode = currentModes!!.single() + assertThat(dndMode.isActive).isFalse() + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -140,11 +213,12 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { // when val result = underTest.onTriggered(null) + verify(zenModeController) .setZen( spyZenMode.capture(), spyConditionId.capture(), - eq(DoNotDisturbQuickAffordanceConfig.TAG) + eq(DoNotDisturbQuickAffordanceConfig.TAG), ) // then @@ -154,7 +228,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsOff_settingFOREVER_setZenWithoutCondition() = + testScope.runTest { + val currentModes by collectLastValue(zenModeRepository.modes) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER) + collectLastValue(underTest.lockScreenState) + runCurrent() + + val result = underTest.onTriggered(null) + runCurrent() + + val dndMode = currentModes!!.single() + assertThat(dndMode.isActive).isTrue() + assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull() + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -169,7 +264,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { .setZen( spyZenMode.capture(), spyConditionId.capture(), - eq(DoNotDisturbQuickAffordanceConfig.TAG) + eq(DoNotDisturbQuickAffordanceConfig.TAG), ) // then @@ -179,7 +274,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() = + testScope.runTest { + val currentModes by collectLastValue(zenModeRepository.modes) + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900) + runCurrent() + + val result = underTest.onTriggered(null) + runCurrent() + + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + val dndMode = currentModes!!.single() + assertThat(dndMode.isActive).isTrue() + assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)) + .isEqualTo(Duration.ofMinutes(-900)) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -194,7 +309,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { .setZen( spyZenMode.capture(), spyConditionId.capture(), - eq(DoNotDisturbQuickAffordanceConfig.TAG) + eq(DoNotDisturbQuickAffordanceConfig.TAG), ) // then @@ -204,7 +319,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() = + testScope.runTest { + val expandable: Expandable = mock() + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT) + whenever(enableZenModeDialog.createDialog()).thenReturn(mock()) + collectLastValue(underTest.lockScreenState) + runCurrent() + + val result = underTest.onTriggered(expandable) + + assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog) + assertEquals( + expandable, + (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable, + ) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() = testScope.runTest { // given val expandable: Expandable = mock() @@ -222,13 +358,31 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog) assertEquals( expandable, - (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable + (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable, ) } @Test + @EnableFlags(Flags.FLAG_MODES_UI) fun lockScreenState_dndAvailableStartsAsTrue_changeToFalse_StateIsHidden() = testScope.runTest { + deviceProvisioningRepository.setDeviceProvisioned(true) + val valueSnapshot = collectLastValue(underTest.lockScreenState) + val secondLastValue = valueSnapshot() + runCurrent() + + deviceProvisioningRepository.setDeviceProvisioned(false) + runCurrent() + val lastValue = valueSnapshot() + + assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun lockScreenState_controllerDndAvailableStartsAsTrue_changeToFalse_StateIsHidden() = + testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor() @@ -246,7 +400,44 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun lockScreenState_dndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun lockScreenState_dndModeStartsAsOff_changeToOn_StateVisible() = + testScope.runTest { + val lockScreenState by collectLastValue(underTest.lockScreenState) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + runCurrent() + + assertThat(lockScreenState) + .isEqualTo( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_off, + ContentDescription.Resource(R.string.dnd_is_off), + ), + ActivationState.Inactive, + ) + ) + + zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id) + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE) + runCurrent() + + assertThat(lockScreenState) + .isEqualTo( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_on, + ContentDescription.Resource(R.string.dnd_is_on), + ), + ActivationState.Active, + ) + ) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun lockScreenState_controllerDndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -265,9 +456,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_dnd_icon_off, - ContentDescription.Resource(R.string.dnd_is_off) + ContentDescription.Resource(R.string.dnd_is_off), ), - ActivationState.Inactive + ActivationState.Inactive, ), secondLastValue, ) @@ -275,9 +466,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_dnd_icon_on, - ContentDescription.Resource(R.string.dnd_is_on) + ContentDescription.Resource(R.string.dnd_is_on), ), - ActivationState.Active + ActivationState.Active, ), lastValue, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index c18deb134075..fac931273ac7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -14,22 +14,6 @@ * limitations under the License. */ -/* - * 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.keyguard.domain.interactor import android.os.PowerManager @@ -47,7 +31,6 @@ import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -129,7 +112,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.DOZING, - testScope + testScope, ) kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE) reset(transitionRepository) @@ -145,10 +128,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Under default conditions, we should transition to LOCKSCREEN when waking up. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -166,10 +146,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible and communal is available, then we should transition to // GLANCEABLE_HUB when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.GLANCEABLE_HUB, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB) } @Test @@ -186,8 +163,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible and communal is available, then we should transition to // GLANCEABLE_HUB when waking up due to power button press. - verify(kosmos.fakeCommunalSceneRepository) - .changeScene(CommunalScenes.Communal, CommunalTransitionKeys.Immediately) + verify(kosmos.fakeCommunalSceneRepository).snapToScene(CommunalScenes.Communal) } @Test @@ -204,10 +180,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is NOT possible but communal is available, then we should transition to // LOCKSCREEN when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -224,10 +197,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible but communal is NOT available, then we should transition to // LOCKSCREEN when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -245,10 +215,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Under default conditions, we should transition to LOCKSCREEN when waking up. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.GLANCEABLE_HUB, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB) } @Test @@ -261,10 +228,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) } @Test @@ -282,10 +246,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index 59f16d70fab5..84b7f5c28265 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -17,17 +17,16 @@ package com.android.systemui.keyguard.domain.interactor -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint @@ -59,8 +58,8 @@ import org.mockito.MockitoAnnotations class KeyguardBlueprintInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val underTest = kosmos.keyguardBlueprintInteractor - private val keyguardBlueprintRepository = kosmos.keyguardBlueprintRepository + private val underTest by lazy { kosmos.keyguardBlueprintInteractor } + private val keyguardBlueprintRepository by lazy { kosmos.keyguardBlueprintRepository } private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository } private val configurationRepository by lazy { kosmos.fakeConfigurationRepository } private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository } @@ -75,7 +74,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { sensorId = 1, strength = SensorStrength.STRONG, sensorType = FingerprintSensorType.POWER_BUTTON, - sensorLocations = mapOf() + sensorLocations = mapOf(), ) } @@ -93,7 +92,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer fun testAppliesSplitShadeBlueprint() { testScope.runTest { val blueprintId by collectLastValue(underTest.blueprintId) @@ -107,7 +106,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + @EnableSceneContainer fun testDoesNotApplySplitShadeBlueprint() { testScope.runTest { val blueprintId by collectLastValue(underTest.blueprintId) @@ -122,7 +121,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer fun fingerprintPropertyInitialized_updatesBlueprint() { testScope.runTest { underTest.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 41c5b7332a4f..ff6ea3a14ff2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor @@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers @@ -71,10 +74,8 @@ class AodBurnInViewModelTest : SysuiTestCase() { private val burnInFlow = MutableStateFlow(BurnInModel()) @Before - @DisableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @DisableSceneContainer fun setUp() { MockitoAnnotations.initMocks(this) whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) @@ -112,18 +113,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(0) assertThat(movement?.translationY).isEqualTo(0) @@ -143,17 +139,12 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) assertThat(movement?.translationY).isEqualTo(30) @@ -166,7 +157,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 0f, - transitionState = TransitionState.STARTED + transitionState = TransitionState.STARTED, ), validateStep = false, ) @@ -180,11 +171,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() = testScope.runTest { - burnInParameters = - burnInParameters.copy( - minViewY = 100, - topInset = 80, - ) + burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80) val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) @@ -193,18 +180,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = -30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) // -20 instead of -30, due to inset of 80 assertThat(movement?.translationY).isEqualTo(-20) @@ -217,7 +199,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 0f, - transitionState = TransitionState.STARTED + transitionState = TransitionState.STARTED, ), validateStep = false, ) @@ -231,11 +213,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() = testScope.runTest { - burnInParameters = - burnInParameters.copy( - minViewY = 100, - topInset = 80, - ) + burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80) val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) @@ -244,18 +222,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = -30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) // -20 instead of -30, due to inset of 80 assertThat(movement?.translationY).isEqualTo(-20) @@ -268,7 +241,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 0f, - transitionState = TransitionState.STARTED + transitionState = TransitionState.STARTED, ), validateStep = false, ) @@ -291,18 +264,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) assertThat(movement?.translationY).isEqualTo(30) @@ -311,9 +279,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_weatherLargeClock() = + fun translationAndScale_sceneContainerOff_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, @@ -321,9 +289,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_weatherSmallClock() = + fun translationAndScale_sceneContainerOff_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, @@ -331,9 +299,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_nonWeatherLargeClock() = + fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, @@ -341,9 +309,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_nonWeatherSmallClock() = + fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, @@ -351,11 +319,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_weatherLargeClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + fun translationAndScale_sceneContainerOn_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, @@ -363,11 +329,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_weatherSmallClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + fun translationAndScale_sceneContainerOn_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, @@ -375,11 +339,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_nonWeatherLargeClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, @@ -387,11 +349,10 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_nonWeatherSmallClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + @Ignore("b/367659687") + fun translationAndScale_sceneContainerOn_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, @@ -421,18 +382,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.LOCKSCREEN, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) assertThat(movement?.translationY).isEqualTo(30) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt index 17e1b53a3ba9..05a6b8785daf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt @@ -16,14 +16,13 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardClockRepository @@ -229,8 +228,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_splitShade_composeLockscreenOn() = + @EnableSceneContainer + fun testSmallClockTop_splitShade_sceneContainerOn() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(true) @@ -244,8 +243,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_splitShade_composeLockscreenOff() = + @DisableSceneContainer + fun testSmallClockTop_splitShade_sceneContainerOff() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(true) @@ -257,8 +256,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_nonSplitShade_composeLockscreenOn() = + @EnableSceneContainer + fun testSmallClockTop_nonSplitShade_sceneContainerOn() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(false) @@ -270,8 +269,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_nonSplitShade_composeLockscreenOff() = + @DisableSceneContainer + fun testSmallClockTop_nonSplitShade_sceneContainerOff() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 763a1a943bf8..385089122fc4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -27,6 +27,7 @@ import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.uiEventLoggerFake import com.android.internal.policy.IKeyguardDismissCallback @@ -88,9 +89,11 @@ import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.repository.Transition import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor @@ -161,6 +164,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(DualShade.FLAG_NAME) fun hydrateVisibility() = testScope.runTest { val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -221,6 +225,87 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(DualShade.FLAG_NAME) + fun hydrateVisibility_dualShade() = + testScope.runTest { + val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene) + val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays) + val isVisible by collectLastValue(sceneInteractor.isVisible) + val transitionStateFlow = + prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = true, + initialSceneKey = Scenes.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone) + assertThat(currentDesiredOverlays).isEmpty() + assertThat(isVisible).isTrue() + + underTest.start() + assertThat(isVisible).isFalse() + + // Expand the notifications shade. + fakeSceneDataSource.pause() + sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Scenes.Gone, + toContent = Overlays.NotificationsShade, + currentScene = Scenes.Gone, + currentOverlays = flowOf(emptySet()), + progress = flowOf(0.5f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + assertThat(isVisible).isTrue() + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + transitionStateFlow.value = + ObservableTransitionState.Idle( + currentScene = Scenes.Gone, + currentOverlays = setOf(Overlays.NotificationsShade), + ) + assertThat(isVisible).isTrue() + + // Collapse the notifications shade. + fakeSceneDataSource.pause() + sceneInteractor.hideOverlay(Overlays.NotificationsShade, "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Overlays.NotificationsShade, + toContent = Scenes.Gone, + currentScene = Scenes.Gone, + currentOverlays = flowOf(setOf(Overlays.NotificationsShade)), + progress = flowOf(0.5f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + assertThat(isVisible).isTrue() + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + transitionStateFlow.value = + ObservableTransitionState.Idle( + currentScene = Scenes.Gone, + currentOverlays = emptySet(), + ) + assertThat(isVisible).isFalse() + + kosmos.headsUpNotificationRepository.setNotifications( + buildNotificationRows(isPinned = true) + ) + assertThat(isVisible).isTrue() + + kosmos.headsUpNotificationRepository.setNotifications( + buildNotificationRows(isPinned = false) + ) + assertThat(isVisible).isFalse() + } + + @Test fun hydrateVisibility_basedOnDeviceProvisioning() = testScope.runTest { val isVisible by collectLastValue(sceneInteractor.isVisible) @@ -1621,6 +1706,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(DualShade.FLAG_NAME) fun hydrateInteractionState_whileLocked() = testScope.runTest { val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen) @@ -1707,6 +1793,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(DualShade.FLAG_NAME) fun hydrateInteractionState_whileUnlocked() = testScope.runTest { val transitionStateFlow = @@ -1795,6 +1882,186 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(DualShade.FLAG_NAME) + fun hydrateInteractionState_dualShade_whileLocked() = + testScope.runTest { + val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays) + val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen) + underTest.start() + runCurrent() + verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true) + assertThat(currentDesiredOverlays).isEmpty() + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Bouncer, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces) + .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true) + }, + ) + + clearInvocations(centralSurfaces) + emulateOverlayTransition( + transitionStateFlow = transitionStateFlow, + toOverlay = Overlays.NotificationsShade, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces) + .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true) + }, + ) + + clearInvocations(centralSurfaces) + emulateOverlayTransition( + transitionStateFlow = transitionStateFlow, + toOverlay = Overlays.QuickSettingsShade, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun hydrateInteractionState_dualShade_whileUnlocked() = + testScope.runTest { + val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays) + val transitionStateFlow = + prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = true, + initialSceneKey = Scenes.Gone, + ) + underTest.start() + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + assertThat(currentDesiredOverlays).isEmpty() + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Bouncer, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Shade, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.QuickSettings, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + } + + @Test fun respondToFalsingDetections() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) @@ -2131,19 +2398,40 @@ class SceneContainerStartableTest : SysuiTestCase() { verifyAfterTransition: (() -> Unit)? = null, ) { val fromScene = sceneInteractor.currentScene.value + val fromOverlays = sceneInteractor.currentOverlays.value sceneInteractor.changeScene(toScene, "reason") runCurrent() verifyBeforeTransition?.invoke() transitionStateFlow.value = - ObservableTransitionState.Transition( - fromScene = fromScene, - toScene = toScene, - currentScene = flowOf(fromScene), - progress = flowOf(0.5f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(true), - ) + if (fromOverlays.isEmpty()) { + // Regular scene-to-scene transition. + ObservableTransitionState.Transition.ChangeScene( + fromScene = fromScene, + toScene = toScene, + currentScene = flowOf(fromScene), + currentOverlays = fromOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } else { + // An overlay is present; hide it. + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = fromOverlays.first(), + fromContent = fromOverlays.first(), + toContent = toScene, + currentScene = fromScene, + currentOverlays = sceneInteractor.currentOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } runCurrent() verifyDuringTransition?.invoke() @@ -2152,6 +2440,60 @@ class SceneContainerStartableTest : SysuiTestCase() { verifyAfterTransition?.invoke() } + private fun TestScope.emulateOverlayTransition( + transitionStateFlow: MutableStateFlow<ObservableTransitionState>, + toOverlay: OverlayKey, + verifyBeforeTransition: (() -> Unit)? = null, + verifyDuringTransition: (() -> Unit)? = null, + verifyAfterTransition: (() -> Unit)? = null, + ) { + val fromScene = sceneInteractor.currentScene.value + val fromOverlays = sceneInteractor.currentOverlays.value + sceneInteractor.showOverlay(toOverlay, "reason") + runCurrent() + verifyBeforeTransition?.invoke() + + transitionStateFlow.value = + if (fromOverlays.isEmpty()) { + // Show a new overlay. + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = toOverlay, + fromContent = fromScene, + toContent = toOverlay, + currentScene = fromScene, + currentOverlays = sceneInteractor.currentOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } else { + // Overlay-to-overlay transition. + ObservableTransitionState.Transition.ReplaceOverlay( + fromOverlay = fromOverlays.first(), + toOverlay = toOverlay, + currentScene = fromScene, + currentOverlays = sceneInteractor.currentOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } + runCurrent() + verifyDuringTransition?.invoke() + + transitionStateFlow.value = + ObservableTransitionState.Idle( + currentScene = fromScene, + currentOverlays = setOf(toOverlay), + ) + runCurrent() + verifyAfterTransition?.invoke() + } + private fun TestScope.prepareState( isDeviceUnlocked: Boolean = false, isBypassEnabled: Boolean = false, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt index 4d69f0ddc4b7..f86337ec63dc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.scene.shared.flag import android.platform.test.flag.junit.FlagsParameterization import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG +import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.flags.andSceneContainer @@ -66,7 +66,7 @@ internal class SceneContainerFlagParameterizationTest : SysuiTestCase() { @Test fun oneDependencyAndSceneContainer() { - val dependentFlag = FLAG_COMPOSE_LOCKSCREEN + val dependentFlag = FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer() Truth.assertThat(result).hasSize(3) Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 40fb7691c0c2..614d51e7ac99 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper.RunWithLooper; import android.view.View; @@ -53,6 +54,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.flags.SceneContainerFlagParameterizationKt; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.kosmos.KosmosJavaAdapter; @@ -466,6 +468,32 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0); } + @Test + @EnableSceneContainer + public void configChanged_boundsUpdate() { + when(mNotificationShadeWindowView.getWidth()).thenReturn(1600); + when(mNotificationShadeWindowView.getHeight()).thenReturn(800); + when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE); + Configuration newConfig = new Configuration(); + // swap width and height in new bounds to simulate auto-rotate + newConfig.windowConfiguration.setBounds(new Rect(0, 0, 800, 1600)); + mNotificationShadeWindowController.onConfigChanged(newConfig); + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), any()); + } + + @Test + @EnableSceneContainer + public void configChanged_boundsDontUpdate() { + when(mNotificationShadeWindowView.getWidth()).thenReturn(1600); + when(mNotificationShadeWindowView.getHeight()).thenReturn(800); + when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE); + Configuration newConfig = new Configuration(); + // same bounds as view's current bounds + newConfig.windowConfiguration.setBounds(new Rect(0, 0, 1600, 800)); + mNotificationShadeWindowController.onConfigChanged(newConfig); + verify(mWindowManager, never()).updateViewLayout(any(), any()); + } + private void setKeyguardShowing() { mNotificationShadeWindowController.setKeyguardShowing(true); mNotificationShadeWindowController.setKeyguardGoingAway(false); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt index fb32855ee2b7..0f6dc0723f42 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.policy.domain.interactor import android.app.AutomaticZenRule +import android.app.Flags import android.app.NotificationManager.Policy +import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.provider.Settings.Secure.ZEN_DURATION import android.provider.Settings.Secure.ZEN_DURATION_FOREVER @@ -32,6 +34,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.shared.settings.data.repository.secureSettingsRepository +import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -50,10 +53,31 @@ class ZenModeInteractorTest : SysuiTestCase() { private val testScope = kosmos.testScope private val zenModeRepository = kosmos.fakeZenModeRepository private val settingsRepository = kosmos.secureSettingsRepository + private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository private val underTest = kosmos.zenModeInteractor @Test + fun isZenAvailable_off() = + testScope.runTest { + val isZenAvailable by collectLastValue(underTest.isZenAvailable) + deviceProvisioningRepository.setDeviceProvisioned(false) + runCurrent() + + assertThat(isZenAvailable).isFalse() + } + + @Test + fun isZenAvailable_on() = + testScope.runTest { + val isZenAvailable by collectLastValue(underTest.isZenAvailable) + deviceProvisioningRepository.setDeviceProvisioned(true) + runCurrent() + + assertThat(isZenAvailable).isTrue() + } + + @Test fun isZenModeEnabled_off() = testScope.runTest { val enabled by collectLastValue(underTest.isZenModeEnabled) @@ -337,4 +361,22 @@ class ZenModeInteractorTest : SysuiTestCase() { runCurrent() assertThat(mainActiveMode).isNull() } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + fun dndMode_flows() = + testScope.runTest { + val dndMode by collectLastValue(underTest.dndMode) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + runCurrent() + + assertThat(dndMode!!.isActive).isFalse() + + zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id) + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE) + runCurrent() + + assertThat(dndMode!!.isActive).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 21a45ecb5922..9dcbe1b591e5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -616,6 +616,71 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { } @Test + fun angleDecreaseAfterCancelAnimation_emitsStartClosingEvent() { + setFoldState(folded = true) + sendHingeAngleEvent(0) + foldUpdates.clear() + + setFoldState(folded = false) + rotationListener.value.onRotationChanged(1) + sendHingeAngleEvent(180) + screenOnStatusProvider.notifyScreenTurningOn() + screenOnStatusProvider.notifyScreenTurnedOn() + + // Start folding + (180 downTo 60).forEach { + sendHingeAngleEvent(it) + } + // Stopped folding and simulate timeout in this posture + simulateTimeout() + + assertThat(foldUpdates) + .containsExactly( + FOLD_UPDATE_START_OPENING, // unfolded + FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation + FOLD_UPDATE_START_CLOSING, // start closing the device + FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state + ) + + } + + @Test + fun angleIncreaseDecreaseAfterHalfUnfold_emitsStartClosingEvent() { + setFoldState(folded = true) + sendHingeAngleEvent(0) + foldUpdates.clear() + + setFoldState(folded = false) + sendHingeAngleEvent(90) + screenOnStatusProvider.notifyScreenTurningOn() + screenOnStatusProvider.notifyScreenTurnedOn() + + // Stopped folding and simulate timeout in this posture + simulateTimeout() + + // Unfold further + (90 until 180).forEach { + sendHingeAngleEvent(it) + } + // Start folding + (180 downTo 90).forEach { + sendHingeAngleEvent(it) + } + + // Stopped folding and simulate timeout in this posture + simulateTimeout() + + assertThat(foldUpdates) + .containsExactly( + FOLD_UPDATE_START_OPENING, // unfolded + FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation + FOLD_UPDATE_START_CLOSING, // start closing the device + FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state + ) + + } + + @Test fun onUnfold_onSmallScreen_emitsStartOpening() { // the new display state might arrive later, so it shouldn't be used to decide to send the // start opening event, but only for the closing. diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f9c2aef5f070..ba3822bd3c23 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3114,6 +3114,10 @@ <string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string> <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] --> <string name="media_output_group_title_suggested_device">Suggested Devices</string> + <!-- Title for input device group. [CHAR LIMIT=NONE] --> + <string name="media_input_group_title">Input</string> + <!-- Title for output device group. [CHAR LIMIT=NONE] --> + <string name="media_output_group_title">Output</string> <!-- Summary for end session dialog. [CHAR LIMIT=NONE] --> <string name="media_output_end_session_dialog_summary">Stop your shared session to move media to another device</string> <!-- Button text for stopping session [CHAR LIMIT=60] --> diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt index 19e7537007bf..b8c30fe9d4a8 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt @@ -76,11 +76,7 @@ class BouncerHapticPlayer @Inject constructor(private val msdlPlayer: dagger.Laz /** Deliver MSDL feedback when the delete key of the pin bouncer is pressed */ fun playDeleteKeyPressFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_DELETE) - /** - * Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed - * - * @return whether MSDL feedback is allowed to play. - */ + /** Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed. */ fun playDeleteKeyLongPressedFeedback() = msdlPlayer.get().playToken(MSDLToken.LONG_PRESS) /** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */ diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index c67b35424cc9..873d1b3cc03d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -21,6 +21,7 @@ import com.android.app.tracing.coroutines.flow.collectLatest import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.lifecycle.ExclusiveActivatable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.channels.Channel @@ -42,6 +43,7 @@ sealed class AuthMethodBouncerViewModel( /** Name to use for performance tracing purposes. */ val traceName: String, + protected val bouncerHapticPlayer: BouncerHapticPlayer? = null, ) : ExclusiveActivatable() { private val _animateFailure = MutableStateFlow(false) @@ -80,6 +82,8 @@ sealed class AuthMethodBouncerViewModel( return@collectLatest } + performAuthenticationHapticFeedback(authenticationResult) + _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED clearInput() } @@ -112,20 +116,23 @@ sealed class AuthMethodBouncerViewModel( /** Returns the input entered so far. */ protected abstract fun getInput(): List<Any> + /** Perform authentication result haptics */ + private fun performAuthenticationHapticFeedback(result: AuthenticationResult) { + if (result == AuthenticationResult.SKIPPED) return + + bouncerHapticPlayer?.playAuthenticationFeedback( + authenticationSucceeded = result == AuthenticationResult.SUCCEEDED + ) + } + /** * Attempts to authenticate the user using the current input value. * * @see BouncerInteractor.authenticate */ - protected fun tryAuthenticate( - input: List<Any> = getInput(), - useAutoConfirm: Boolean = false, - ) { + protected fun tryAuthenticate(input: List<Any> = getInput(), useAutoConfirm: Boolean = false) { authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm)) } - private data class AuthenticationRequest( - val input: List<Any>, - val useAutoConfirm: Boolean, - ) + private data class AuthenticationRequest(val input: List<Any>, val useAutoConfirm: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt index 0aada06a7eb7..0bcb58dee934 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.qualifiers.Application @@ -61,6 +62,7 @@ constructor( private val pinViewModelFactory: PinBouncerViewModel.Factory, private val patternViewModelFactory: PatternBouncerViewModel.Factory, private val passwordViewModelFactory: PasswordBouncerViewModel.Factory, + private val bouncerHapticPlayer: BouncerHapticPlayer, ) : ExclusiveActivatable() { private val _selectedUserImage = MutableStateFlow<Bitmap?>(null) val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow() @@ -162,10 +164,7 @@ constructor( } launch { - combine( - userSwitcher.users, - userSwitcher.menu, - ) { users, actions -> + combine(userSwitcher.users, userSwitcher.menu) { users, actions -> users.map { user -> UserSwitcherDropdownItemViewModel( icon = Icon.Loaded(user.image, contentDescription = null), @@ -178,7 +177,7 @@ constructor( icon = Icon.Resource( action.iconResourceId, - contentDescription = null + contentDescription = null, ), text = Text.Resource(action.textResourceId), onClick = action.onClicked, @@ -226,7 +225,7 @@ constructor( } private fun getChildViewModel( - authenticationMethod: AuthenticationMethodModel, + authenticationMethod: AuthenticationMethodModel ): AuthMethodBouncerViewModel? { // If the current child view-model matches the authentication method, reuse it instead of // creating a new instance. @@ -241,12 +240,14 @@ constructor( authenticationMethod = authenticationMethod, onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, + bouncerHapticPlayer = bouncerHapticPlayer, ) is AuthenticationMethodModel.Sim -> pinViewModelFactory.create( authenticationMethod = authenticationMethod, onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, + bouncerHapticPlayer = bouncerHapticPlayer, ) is AuthenticationMethodModel.Password -> passwordViewModelFactory.create( @@ -257,6 +258,7 @@ constructor( patternViewModelFactory.create( onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, + bouncerHapticPlayer = bouncerHapticPlayer, ) else -> null } @@ -317,10 +319,7 @@ constructor( return when { // The wipe dialog takes priority over the lockout dialog. wipeText != null -> - DialogViewModel( - text = wipeText, - onDismiss = { wipeDialogMessage.value = null }, - ) + DialogViewModel(text = wipeText, onDismiss = { wipeDialogMessage.value = null }) lockoutText != null -> DialogViewModel( text = lockoutText, @@ -338,7 +337,7 @@ constructor( fun onKeyEvent(keyEvent: KeyEvent): Boolean { return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent( keyEvent.type, - keyEvent.nativeKeyEvent.keyCode + keyEvent.nativeKeyEvent.keyCode, ) ?: false } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 0a866b43429f..158f102ccdb3 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -18,9 +18,11 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import android.util.TypedValue +import android.view.View import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.res.R import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -35,7 +37,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch /** Holds UI state and handles user input for the pattern bouncer UI. */ @@ -44,6 +45,7 @@ class PatternBouncerViewModel constructor( private val applicationContext: Context, interactor: BouncerInteractor, + @Assisted bouncerHapticPlayer: BouncerHapticPlayer, @Assisted isInputEnabled: StateFlow<Boolean>, @Assisted private val onIntentionalUserInput: () -> Unit, ) : @@ -51,6 +53,7 @@ constructor( interactor = interactor, isInputEnabled = isInputEnabled, traceName = "PatternBouncerViewModel", + bouncerHapticPlayer = bouncerHapticPlayer, ) { /** The number of columns in the dot grid. */ @@ -190,14 +193,7 @@ constructor( private fun defaultDots(): List<PatternDotViewModel> { return buildList { (0 until columnCount).forEach { x -> - (0 until rowCount).forEach { y -> - add( - PatternDotViewModel( - x = x, - y = y, - ) - ) - } + (0 until rowCount).forEach { y -> add(PatternDotViewModel(x = x, y = y)) } } } } @@ -207,14 +203,17 @@ constructor( applicationContext.resources.getValue( com.android.internal.R.dimen.lock_pattern_dot_hit_factor, outValue, - true + true, ) max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR) } + fun performDotFeedback(view: View?) = bouncerHapticPlayer?.playPatternDotFeedback(view) + @AssistedFactory interface Factory { fun create( + bouncerHapticPlayer: BouncerHapticPlayer, isInputEnabled: StateFlow<Boolean>, onIntentionalUserInput: () -> Unit, ): PatternBouncerViewModel @@ -231,7 +230,7 @@ constructor( */ private fun PatternDotViewModel.isOnLineSegment( first: PatternDotViewModel, - second: PatternDotViewModel + second: PatternDotViewModel, ): Boolean { val anotherPoint = this // No need to consider any points outside the bounds of two end points @@ -253,14 +252,8 @@ private fun Int.isBetween(a: Int, b: Int): Boolean { return (this in a..b) || (this in b..a) } -data class PatternDotViewModel( - val x: Int, - val y: Int, -) { +data class PatternDotViewModel(val x: Int, val y: Int) { fun toCoordinate(): AuthenticationPatternCoordinate { - return AuthenticationPatternCoordinate( - x = x, - y = y, - ) + return AuthenticationPatternCoordinate(x = x, y = y) } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index da29c6230cd8..0cb4260e4d7f 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -19,12 +19,14 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context +import android.view.HapticFeedbackConstants import android.view.KeyEvent.KEYCODE_0 import android.view.KeyEvent.KEYCODE_9 import android.view.KeyEvent.KEYCODE_DEL import android.view.KeyEvent.KEYCODE_NUMPAD_0 import android.view.KeyEvent.KEYCODE_NUMPAD_9 import android.view.KeyEvent.isConfirmKey +import android.view.View import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType import com.android.keyguard.PinShapeAdapter @@ -32,6 +34,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.res.R import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -56,6 +59,7 @@ constructor( applicationContext: Context, interactor: BouncerInteractor, private val simBouncerInteractor: SimBouncerInteractor, + @Assisted bouncerHapticPlayer: BouncerHapticPlayer, @Assisted isInputEnabled: StateFlow<Boolean>, @Assisted private val onIntentionalUserInput: () -> Unit, @Assisted override val authenticationMethod: AuthenticationMethodModel, @@ -64,6 +68,7 @@ constructor( interactor = interactor, isInputEnabled = isInputEnabled, traceName = "PinBouncerViewModel", + bouncerHapticPlayer = bouncerHapticPlayer, ) { /** * Whether the sim-related UI in the pin view is showing. @@ -126,10 +131,9 @@ constructor( .collect { _hintedPinLength.value = it } } launch { - combine( - mutablePinInput, - interactor.isAutoConfirmEnabled, - ) { mutablePinEntries, isAutoConfirmEnabled -> + combine(mutablePinInput, interactor.isAutoConfirmEnabled) { + mutablePinEntries, + isAutoConfirmEnabled -> computeBackspaceButtonAppearance( pinInput = mutablePinEntries, isAutoConfirmEnabled = isAutoConfirmEnabled, @@ -183,8 +187,22 @@ constructor( mutablePinInput.value = mutablePinInput.value.deleteLast() } + fun onBackspaceButtonPressed(view: View?) { + if (bouncerHapticPlayer?.isEnabled == true) { + bouncerHapticPlayer.playDeleteKeyPressFeedback() + } else { + view?.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + } + /** Notifies that the user long-pressed the backspace button. */ fun onBackspaceButtonLongPressed() { + if (bouncerHapticPlayer?.isEnabled == true) { + bouncerHapticPlayer.playDeleteKeyLongPressedFeedback() + } clearInput() } @@ -266,13 +284,24 @@ constructor( } } - /** Notifies that the user has pressed down on a digit button. */ - fun onDigitButtonDown() { + /** + * Notifies that the user has pressed down on a digit button. This function also performs haptic + * feedback on the view. + */ + fun onDigitButtonDown(view: View?) { if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) { // Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button // touch. super.onDown() } + if (bouncerHapticPlayer?.isEnabled == true) { + bouncerHapticPlayer.playNumpadKeyFeedback() + } else { + view?.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } } @AssistedFactory @@ -281,6 +310,7 @@ constructor( isInputEnabled: StateFlow<Boolean>, onIntentionalUserInput: () -> Unit, authenticationMethod: AuthenticationMethodModel, + bouncerHapticPlayer: BouncerHapticPlayer, ): PinBouncerViewModel } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 769976ef5058..ae4b679dd4b8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -282,6 +282,8 @@ public class FalsingDataProvider { return !mRecentKeyEvents.isEmpty(); } + // Deprecated in favor of {@code isTouchScreenSource}, b/329221787 + @Deprecated public boolean isFromTrackpad() { if (Flags.nonTouchscreenDevicesBypassFalsing()) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index c7a47b18f467..1ada56dea45a 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -30,6 +30,7 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.os.Build; +import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -37,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.user.utils.UserScopedService; import javax.inject.Inject; import javax.inject.Provider; @@ -67,13 +69,13 @@ public class ClipboardListener implements public ClipboardListener(Context context, Provider<ClipboardOverlayController> clipboardOverlayControllerProvider, ClipboardToast clipboardToast, - ClipboardManager clipboardManager, + UserScopedService<ClipboardManager> clipboardManager, KeyguardManager keyguardManager, UiEventLogger uiEventLogger) { mContext = context; mOverlayProvider = clipboardOverlayControllerProvider; mClipboardToast = clipboardToast; - mClipboardManager = clipboardManager; + mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT); mKeyguardManager = keyguardManager; mUiEventLogger = uiEventLogger; } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 6894a7238f37..08a7c395e57f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -256,9 +256,8 @@ constructor( Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) } from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> { - // Make sure the communal hub is showing (immediately, not fading in) when - // transitioning from dozing to hub. - Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately) + // Make sure the communal hub is showing when transitioning from dozing to hub. + Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade) } else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt index 11fb2332dc5f..78156dbc8964 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt @@ -30,6 +30,4 @@ object CommunalTransitionKeys { val ToEditMode = TransitionKey("ToEditMode") /** Transition to the glanceable hub after exiting edit mode */ val FromEditMode = TransitionKey("FromEditMode") - /** Immediately transitions without any delay */ - val Immediately = TransitionKey("Immediately") } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 8818c3af4916..8f913ff01337 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -733,9 +733,8 @@ public class FrameworkServicesModule { } @Provides - @Singleton - static ClipboardManager provideClipboardManager(Context context) { - return context.getSystemService(ClipboardManager.class); + static UserScopedService<ClipboardManager> provideClipboardManager(Context context) { + return new UserScopedServiceImpl<>(context, ClipboardManager.class); } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index e17e530a03d9..5259c5dca39f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -26,7 +26,9 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus import com.android.systemui.flags.SystemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.TrustInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject @@ -36,6 +38,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -57,6 +60,7 @@ constructor( private val powerInteractor: PowerInteractor, private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, private val systemPropertiesHelper: SystemPropertiesHelper, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { private val deviceUnlockSource = @@ -74,7 +78,7 @@ constructor( trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent }, authenticationInteractor.onAuthenticationResult .filter { it } - .map { DeviceUnlockSource.BouncerInput } + .map { DeviceUnlockSource.BouncerInput }, ) private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled @@ -170,10 +174,20 @@ constructor( combine( powerInteractor.isAsleep, isInLockdown, - ::Pair, + keyguardTransitionInteractor + .transitionValue(KeyguardState.AOD) + .map { it == 1f } + .distinctUntilChanged(), + ::Triple, ) - .flatMapLatestConflated { (isAsleep, isInLockdown) -> - if (isAsleep || isInLockdown) { + .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) -> + val isForceLocked = + when { + isAsleep && !isAod -> true + isInLockdown -> true + else -> false + } + if (isForceLocked) { flowOf(DeviceUnlockStatus(false, null)) } else { deviceUnlockSource.map { DeviceUnlockStatus(true, it) } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 6318dc000c21..0b775ab486bd 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -31,9 +31,7 @@ import com.android.systemui.Flags.statusBarCallChipNotificationIcon import com.android.systemui.Flags.statusBarScreenSharingChips import com.android.systemui.Flags.statusBarUseReposForCallChip import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag @@ -62,10 +60,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha // SceneContainer dependencies SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta } - // ComposeLockscreen dependencies - ComposeLockscreen.token dependsOn KeyguardBottomAreaRefactor.token - ComposeLockscreen.token dependsOn MigrateClocksToBlueprint.token - // CommunalHub dependencies communalHub dependsOn MigrateClocksToBlueprint.token @@ -99,7 +93,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha get() = FlagToken( FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, - statusBarCallChipNotificationIcon() + statusBarCallChipNotificationIcon(), ) private inline val statusBarScreenSharingChipsToken diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt index e50c05c7f6ed..e09e1987698d 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -29,6 +29,7 @@ import com.android.systemui.animation.Expandable import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.QSLog +import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController @@ -44,12 +45,12 @@ import javax.inject.Inject * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects. * @property[effectDuration] The duration of the effect in ms. */ -// TODO(b/332902869): In addition from being injectable, we can consider making it a singleton class QSLongPressEffect @Inject constructor( private val vibratorHelper: VibratorHelper?, private val keyguardStateController: KeyguardStateController, + private val falsingManager: FalsingManager, @QSLog private val logBuffer: LogBuffer, ) { @@ -72,7 +73,7 @@ constructor( private val durations = vibratorHelper?.getPrimitiveDurations( VibrationEffect.Composition.PRIMITIVE_LOW_TICK, - VibrationEffect.Composition.PRIMITIVE_SPIN + VibrationEffect.Composition.PRIMITIVE_SPIN, ) private var longPressHint: VibrationEffect? = null @@ -152,15 +153,27 @@ constructor( logEvent(qsTile?.tileSpec, state, "animation completed") when (state) { State.RUNNING_FORWARD -> { - vibrate(snapEffect) - if (keyguardStateController.isUnlocked) { + val wasFalseLongTap = falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) + if (wasFalseLongTap) { + callback?.onResetProperties() + setState(State.IDLE) + logEvent(qsTile?.tileSpec, state, "false long click. No action triggered") + } else if (keyguardStateController.isUnlocked) { + vibrate(snapEffect) setState(State.LONG_CLICKED) + qsTile?.longClick(expandable) + logEvent(qsTile?.tileSpec, state, "long click action triggered") } else { + vibrate(snapEffect) callback?.onResetProperties() setState(State.IDLE) + qsTile?.longClick(expandable) + logEvent( + qsTile?.tileSpec, + state, + "properties reset and long click action triggered", + ) } - logEvent(qsTile?.tileSpec, state, "long click action triggered") - qsTile?.longClick(expandable) } State.RUNNING_BACKWARDS_FROM_UP -> { callback?.onEffectFinishedReversing() @@ -236,7 +249,7 @@ constructor( LongPressHapticBuilder.createLongPressHint( durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION, durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION, - effectDuration + effectDuration, ) setState(State.IDLE) return true @@ -265,7 +278,7 @@ constructor( } override fun dialogTransitionController( - cuj: DialogCuj?, + cuj: DialogCuj? ): DialogTransitionAnimator.Controller? = DialogTransitionAnimator.Controller.fromView(view, cuj) } @@ -298,7 +311,7 @@ constructor( str2 = event str3 = state.name }, - { "[long-press effect on $str1 tile] $str2 on state: $str3" } + { "[long-press effect on $str1 tile] $str2 on state: $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt index 95251749132d..48f5cb6dc219 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt @@ -16,27 +16,27 @@ package com.android.systemui.inputdevice.tutorial +import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState +import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen as KeyboardTouchpadTutorialScreen +import com.android.systemui.log.ConstantStringsLogger +import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.MessageInitializer +import com.android.systemui.log.core.MessagePrinter import com.android.systemui.log.dagger.InputDeviceTutorialLog -import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen -import com.google.errorprone.annotations.CompileTimeConstant +import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen as TouchpadTutorialScreen import javax.inject.Inject private const val TAG = "InputDeviceTutorial" class InputDeviceTutorialLogger @Inject -constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) { +constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) : + ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { - fun log(@CompileTimeConstant s: String) { - buffer.log(TAG, LogLevel.INFO, message = s) - } - - fun logGoingToScreen(screen: Screen, context: TutorialContext) { - buffer.log( - TAG, - LogLevel.INFO, + fun logGoingToScreen(screen: TouchpadTutorialScreen, context: TutorialContext) { + logInfo( { str1 = screen.toString() str2 = context.string @@ -46,7 +46,58 @@ constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) { } fun logCloseTutorial(context: TutorialContext) { - buffer.log(TAG, LogLevel.INFO, { str1 = context.string }, { "Closing $str1" }) + logInfo({ str1 = context.string }, { "Closing $str1" }) + } + + fun logOpenTutorial(context: TutorialContext) { + logInfo({ str1 = context.string }, { "Opening $str1" }) + } + + fun logNextScreenMissingHardware(nextScreen: KeyboardTouchpadTutorialScreen) { + buffer.log( + TAG, + LogLevel.WARNING, + { str1 = nextScreen.toString() }, + { "next screen should be $str1 but required hardware is missing" } + ) + } + + fun logNextScreen(nextScreen: KeyboardTouchpadTutorialScreen) { + logInfo({ str1 = nextScreen.toString() }, { "going to $str1 screen" }) + } + + fun logNewConnectionState(connectionState: ConnectionState) { + logInfo( + { + bool1 = connectionState.touchpadConnected + bool2 = connectionState.keyboardConnected + }, + { "Received connection state: touchpad connected: $bool1 keyboard connected: $bool2" } + ) + } + + fun logMovingBetweenScreens( + previousScreen: KeyboardTouchpadTutorialScreen?, + currentScreen: KeyboardTouchpadTutorialScreen + ) { + logInfo( + { + str1 = previousScreen?.toString() ?: "NO_SCREEN" + str2 = currentScreen.toString() + }, + { "Moving from $str1 screen to $str2 screen" } + ) + } + + fun logGoingBack(previousScreen: KeyboardTouchpadTutorialScreen) { + logInfo({ str1 = previousScreen.toString() }, { "Going back to $str1 screen" }) + } + + private inline fun logInfo( + messageInitializer: MessageInitializer, + noinline messagePrinter: MessagePrinter + ) { + buffer.log(TAG, LogLevel.INFO, messageInitializer, messagePrinter) } enum class TutorialContext(val string: String) { diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt index 1adc285e6bb5..c130c6c7fe12 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt @@ -28,6 +28,8 @@ import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import com.android.compose.theme.PlatformTheme +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel @@ -48,6 +50,7 @@ class KeyboardTouchpadTutorialActivity constructor( private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider, private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>, + private val logger: InputDeviceTutorialLogger, ) : ComponentActivity() { companion object { @@ -74,6 +77,7 @@ constructor( lifecycleScope.launch { vm.closeActivity.collect { finish -> if (finish) { + logger.logCloseTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL) finish() } } @@ -81,6 +85,9 @@ constructor( setContent { PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) } } + if (savedInstanceState == null) { + logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt index 315c102e94d0..5cf19677a98e 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY @@ -47,6 +48,7 @@ class KeyboardTouchpadTutorialViewModel( private val gesturesInteractor: Optional<TouchpadGesturesInteractor>, private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor, private val hasTouchpadTutorialScreens: Boolean, + private val logger: InputDeviceTutorialLogger, handle: SavedStateHandle ) : ViewModel(), DefaultLifecycleObserver { @@ -68,7 +70,10 @@ class KeyboardTouchpadTutorialViewModel( init { viewModelScope.launch { - keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it } + keyboardTouchpadConnectionInteractor.connectionState.collect { + logger.logNewConnectionState(connectionState) + connectionState = it + } } viewModelScope.launch { @@ -89,7 +94,14 @@ class KeyboardTouchpadTutorialViewModel( viewModelScope.launch { // close activity if screen requires touchpad but we don't have it. This can only happen // when current sysui build doesn't contain touchpad module dependency - _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true } + _screen + .filterNot { it.canBeShown() } + .collect { + logger.e( + "Touchpad is connected but touchpad module is missing, something went wrong" + ) + _closeActivity.value = true + } } } @@ -114,11 +126,14 @@ class KeyboardTouchpadTutorialViewModel( if (requiredHardwarePresent(nextScreen)) { break } + logger.logNextScreenMissingHardware(nextScreen) nextScreen = nextScreen.next() } if (nextScreen == null) { + logger.d("Final screen reached, closing tutorial") _closeActivity.value = true } else { + logger.logNextScreen(nextScreen) _screen.value = nextScreen screensBackStack.add(nextScreen) } @@ -127,6 +142,7 @@ class KeyboardTouchpadTutorialViewModel( private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) { + logger.logMovingBetweenScreens(previousScreen, currentScreen) if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return previousScreen?.let { clearDeviceStateForScreen(it) } when (currentScreen.requiredHardware) { @@ -153,6 +169,7 @@ class KeyboardTouchpadTutorialViewModel( _closeActivity.value = true } else { screensBackStack.removeLast() + logger.logGoingBack(screensBackStack.last()) _screen.value = screensBackStack.last() } } @@ -162,6 +179,7 @@ class KeyboardTouchpadTutorialViewModel( constructor( private val gesturesInteractor: Optional<TouchpadGesturesInteractor>, private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor, + private val logger: InputDeviceTutorialLogger, @Assisted private val hasTouchpadTutorialScreens: Boolean, ) : AbstractSavedStateViewModelFactory() { @@ -180,6 +198,7 @@ class KeyboardTouchpadTutorialViewModel( gesturesInteractor, keyboardTouchpadConnected, hasTouchpadTutorialScreens, + logger, handle ) as T diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index df0f10acac1f..416eabae78eb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -24,12 +24,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.END -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import androidx.constraintlayout.widget.ConstraintSet.START -import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout @@ -47,7 +41,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder @@ -128,7 +121,7 @@ constructor( keyguardStatusViewComponentFactory.build( LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, null) as KeyguardStatusView, - context.display + context.display, ) val controller = statusViewComponent.keyguardStatusViewController controller.init() @@ -143,29 +136,12 @@ constructor( initializeViews() if (!SceneContainerFlag.isEnabled) { - if (ComposeLockscreen.isEnabled) { - val composeView = - createLockscreen( - context = context, - viewModelFactory = lockscreenContentViewModelFactory, - blueprints = lockscreenSceneBlueprintsLazy.get(), - ) - composeView.id = View.generateViewId() - val cs = ConstraintSet() - cs.clone(keyguardRootView) - cs.connect(composeView.id, START, PARENT_ID, START) - cs.connect(composeView.id, END, PARENT_ID, END) - cs.connect(composeView.id, TOP, PARENT_ID, TOP) - cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM) - keyguardRootView.addView(composeView) - } else { - KeyguardBlueprintViewBinder.bind( - keyguardRootView, - keyguardBlueprintViewModel, - keyguardClockViewModel, - smartspaceViewModel, - ) - } + KeyguardBlueprintViewBinder.bind( + keyguardRootView, + keyguardBlueprintViewModel, + keyguardClockViewModel, + smartspaceViewModel, + ) } if (deviceEntryUnlockTrackerViewBinder.isPresent) { deviceEntryUnlockTrackerViewBinder.get().bind(keyguardRootView) @@ -247,7 +223,7 @@ constructor( LockscreenContent( viewModelFactory = viewModelFactory, blueprints = sceneBlueprints, - clockInteractor = clockInteractor + clockInteractor = clockInteractor, ) ) { Content(modifier = Modifier.fillMaxSize()) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt index 406b9f6e6a0d..be873344b719 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt @@ -25,7 +25,9 @@ import android.provider.Settings.Global.ZEN_MODE_OFF import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import android.service.notification.ZenModeConfig +import android.util.Log import com.android.settingslib.notification.modes.EnableZenModeDialog +import com.android.settingslib.notification.modes.ZenMode import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging @@ -35,30 +37,38 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.modes.shared.ModesUi import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn @SysUISingleton class DoNotDisturbQuickAffordanceConfig constructor( private val context: Context, private val controller: ZenModeController, + private val interactor: ZenModeInteractor, private val secureSettings: SecureSettings, private val userTracker: UserTracker, @Background private val backgroundDispatcher: CoroutineDispatcher, + @Background private val backgroundScope: CoroutineScope, private val testConditionId: Uri?, testDialog: EnableZenModeDialog?, ) : KeyguardQuickAffordanceConfig { @@ -67,15 +77,45 @@ constructor( constructor( context: Context, controller: ZenModeController, + interactor: ZenModeInteractor, secureSettings: SecureSettings, userTracker: UserTracker, @Background backgroundDispatcher: CoroutineDispatcher, - ) : this(context, controller, secureSettings, userTracker, backgroundDispatcher, null, null) + @Background backgroundScope: CoroutineScope, + ) : this( + context, + controller, + interactor, + secureSettings, + userTracker, + backgroundDispatcher, + backgroundScope, + null, + null, + ) - private var dndMode: Int = 0 - private var isAvailable = false + private var zenMode: Int = 0 + private var oldIsAvailable = false private var settingsValue: Int = 0 + private val dndMode: StateFlow<ZenMode?> by lazy { + ModesUi.assertInNewMode() + interactor.dndMode.stateIn( + scope = backgroundScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + } + + private val isAvailable: StateFlow<Boolean> by lazy { + ModesUi.assertInNewMode() + interactor.isZenAvailable.stateIn( + scope = backgroundScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + } + private val conditionUri: Uri get() = testConditionId @@ -104,42 +144,68 @@ constructor( override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = - combine( - conflatedCallbackFlow { - val callback = - object : ZenModeController.Callback { - override fun onZenChanged(zen: Int) { - dndMode = zen - trySendWithFailureLogging(updateState(), TAG) - } + if (ModesUi.isEnabled) { + combine(isAvailable, dndMode) { isAvailable, dndMode -> + if (!isAvailable) { + KeyguardQuickAffordanceConfig.LockScreenState.Hidden + } else if (dndMode?.isActive == true) { + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_on, + ContentDescription.Resource(R.string.dnd_is_on), + ), + ActivationState.Active, + ) + } else { + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_off, + ContentDescription.Resource(R.string.dnd_is_off), + ), + ActivationState.Inactive, + ) + } + } + } else { + combine( + conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onZenChanged(zen: Int) { + zenMode = zen + trySendWithFailureLogging(updateState(), TAG) + } - override fun onZenAvailableChanged(available: Boolean) { - isAvailable = available - trySendWithFailureLogging(updateState(), TAG) + override fun onZenAvailableChanged(available: Boolean) { + oldIsAvailable = available + trySendWithFailureLogging(updateState(), TAG) + } } - } - dndMode = controller.zen - isAvailable = controller.isZenAvailable - trySendWithFailureLogging(updateState(), TAG) - - controller.addCallback(callback) - - awaitClose { controller.removeCallback(callback) } - }, - secureSettings - .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION) - .onStart { emit(Unit) } - .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) } - .flowOn(backgroundDispatcher) - .distinctUntilChanged() - .onEach { settingsValue = it } - ) { callbackFlowValue, _ -> - callbackFlowValue + zenMode = controller.zen + oldIsAvailable = controller.isZenAvailable + trySendWithFailureLogging(updateState(), TAG) + + controller.addCallback(callback) + + awaitClose { controller.removeCallback(callback) } + }, + secureSettings + .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION) + .onStart { emit(Unit) } + .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) } + .flowOn(backgroundDispatcher) + .distinctUntilChanged() + .onEach { settingsValue = it }, + ) { callbackFlowValue, _ -> + callbackFlowValue + } } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { - return if (controller.isZenAvailable) { + val isZenAvailable = if (ModesUi.isEnabled) isAvailable.value else controller.isZenAvailable + + return if (isZenAvailable) { KeyguardQuickAffordanceConfig.PickerScreenState.Default( configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) ) @@ -151,32 +217,63 @@ constructor( override fun onTriggered( expandable: Expandable? ): KeyguardQuickAffordanceConfig.OnTriggeredResult { - return when { - !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled - dndMode != ZEN_MODE_OFF -> { - controller.setZen(ZEN_MODE_OFF, null, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled - } - settingsValue == ZEN_DURATION_PROMPT -> - KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( - dialog.createDialog(), - expandable - ) - settingsValue == ZEN_DURATION_FOREVER -> { - controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG) + return if (ModesUi.isEnabled) { + if (!isAvailable.value) { KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } else { + val dnd = dndMode.value + if (dnd == null) { + Log.wtf(TAG, "Triggered DND but it's null!?") + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + if (dnd.isActive) { + interactor.deactivateMode(dnd) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } else { + if (interactor.shouldAskForZenDuration(dnd)) { + // NOTE: The dialog handles turning on the mode itself. + return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( + dialog.createDialog(), + expandable, + ) + } else { + interactor.activateMode(dnd) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + } } - else -> { - controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } else { + when { + !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + zenMode != ZEN_MODE_OFF -> { + controller.setZen(ZEN_MODE_OFF, null, TAG) + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + settingsValue == ZEN_DURATION_PROMPT -> + KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( + dialog.createDialog(), + expandable, + ) + + settingsValue == ZEN_DURATION_FOREVER -> { + controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG) + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + else -> { + controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG) + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } } } } private fun updateState(): KeyguardQuickAffordanceConfig.LockScreenState { - return if (!isAvailable) { + ModesUi.assertInLegacyMode() + return if (!oldIsAvailable) { KeyguardQuickAffordanceConfig.LockScreenState.Hidden - } else if (dndMode == ZEN_MODE_OFF) { + } else if (zenMode == ZEN_MODE_OFF) { KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_dnd_icon_off, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 80a0cee4f319..b0820a747e17 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -24,7 +24,6 @@ import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -95,10 +94,7 @@ constructor( scope.launch { powerInteractor.isAwake .filterRelevantKeyguardStateAnd { isAwake -> isAwake } - .sample( - keyguardInteractor.biometricUnlockState, - ::Pair, - ) + .sample(keyguardInteractor.biometricUnlockState, ::Pair) .collect { ( _, @@ -203,21 +199,21 @@ constructor( if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.GONE, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (primaryBouncerShowing) { if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.PRIMARY_BOUNCER, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.GLANCEABLE_HUB, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) { @@ -227,7 +223,7 @@ constructor( } else { startTransitionTo( KeyguardState.LOCKSCREEN, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } @@ -237,11 +233,9 @@ constructor( private suspend fun transitionToGlanceableHub() { if (communalSceneKtfRefactor()) { - communalSceneInteractor.changeScene( + communalSceneInteractor.snapToScene( newScene = CommunalScenes.Communal, loggingReason = "from dozing to hub", - // Immediately show the hub when transitioning from dozing to hub. - transitionKey = CommunalTransitionKeys.Immediately, ) } else { startTransitionTo(KeyguardState.GLANCEABLE_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index 199caa168e31..7759298cb32a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -162,10 +162,9 @@ constructor( .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep } .collect { if (communalSceneKtfRefactor()) { - communalSceneInteractor.changeScene( + communalSceneInteractor.snapToScene( newScene = CommunalScenes.Blank, loggingReason = "hub to dozing", - transitionKey = CommunalTransitionKeys.Immediately, keyguardState = KeyguardState.DOZING, ) } else { @@ -210,12 +209,12 @@ constructor( // ends, to avoid transitioning to OCCLUDED erroneously when exiting // the dream. .debounce(100.milliseconds), - ::Pair + ::Pair, ) .sampleFilter( // When launching activities from widgets on the hub, we have a // custom occlusion animation. - communalSceneInteractor.isLaunchingWidget, + communalSceneInteractor.isLaunchingWidget ) { launchingWidget -> !launchingWidget } @@ -253,7 +252,7 @@ constructor( noneOf( // When launching activities from widgets on the hub, we wait to change // scenes until the activity launch is complete. - communalSceneInteractor.isLaunchingWidget, + communalSceneInteractor.isLaunchingWidget ), ) .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } @@ -270,7 +269,7 @@ constructor( newScene = CommunalScenes.Blank, loggingReason = "hub to gone", transitionKey = CommunalTransitionKeys.SimpleFade, - keyguardState = KeyguardState.GONE + keyguardState = KeyguardState.GONE, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 7afc7596a994..6932eb51e141 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -25,13 +25,13 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -64,7 +64,7 @@ constructor( /** Current BlueprintId */ val blueprintId = shadeInteractor.isShadeLayoutWide.map { isShadeLayoutWide -> - val useSplitShade = isShadeLayoutWide && !ComposeLockscreen.isEnabled + val useSplitShade = isShadeLayoutWide && !SceneContainerFlag.isEnabled when { useSplitShade -> SplitShadeKeyguardBlueprint.ID else -> DefaultKeyguardBlueprint.DEFAULT diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index ed82159e6160..deb0b2d8f848 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -59,7 +59,6 @@ import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters @@ -72,6 +71,7 @@ import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.VibratorHelper @@ -241,7 +241,7 @@ object KeyguardRootViewBinder { disposables += view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - if (ComposeLockscreen.isEnabled) { + if (SceneContainerFlag.isEnabled) { view.setViewTreeOnBackPressedDispatcherOwner( object : OnBackPressedDispatcherOwner { override val onBackPressedDispatcher = @@ -261,10 +261,7 @@ object KeyguardRootViewBinder { -> if (biometricMessage?.message != null) { chipbarCoordinator!!.displayView( - createChipbarInfo( - biometricMessage.message, - R.drawable.ic_lock, - ) + createChipbarInfo(biometricMessage.message, R.drawable.ic_lock) ) } else { chipbarCoordinator!!.removeView(ID, "occludingAppMsgNull") @@ -327,12 +324,16 @@ object KeyguardRootViewBinder { .getDimensionPixelSize(R.dimen.shelf_appear_translation) .stateIn(this) viewModel.isNotifIconContainerVisible.collect { isVisible -> - childViews[aodNotificationIconContainerId] - ?.setAodNotifIconContainerIsVisible( - isVisible, - iconsAppearTranslationPx.value, - screenOffAnimationController, - ) + if (isVisible.value) { + blueprintViewModel.refreshBlueprint() + } else { + childViews[aodNotificationIconContainerId] + ?.setAodNotifIconContainerIsVisible( + isVisible, + iconsAppearTranslationPx.value, + screenOffAnimationController, + ) + } } } @@ -382,7 +383,7 @@ object KeyguardRootViewBinder { if (msdlFeedback()) { msdlPlayer?.playToken( MSDLToken.UNLOCK, - authInteractionProperties + authInteractionProperties, ) } else { vibratorHelper.performHapticFeedback( @@ -398,7 +399,7 @@ object KeyguardRootViewBinder { if (msdlFeedback()) { msdlPlayer?.playToken( MSDLToken.FAILURE, - authInteractionProperties + authInteractionProperties, ) } else { vibratorHelper.performHapticFeedback( @@ -425,7 +426,7 @@ object KeyguardRootViewBinder { blueprintViewModel, clockViewModel, childViews, - burnInParams + burnInParams, ) ) @@ -464,11 +465,7 @@ object KeyguardRootViewBinder { */ private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo { return ChipbarInfo( - startIcon = - TintedIcon( - Icon.Resource(icon, null), - ChipbarInfo.DEFAULT_ICON_TINT, - ), + startIcon = TintedIcon(Icon.Resource(icon, null), ChipbarInfo.DEFAULT_ICON_TINT), text = Text.Loaded(message), endItem = null, vibrationEffect = null, @@ -499,7 +496,7 @@ object KeyguardRootViewBinder { oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int + oldBottom: Int, ) { // After layout, ensure the notifications are positioned correctly childViews[nsslPlaceholderId]?.let { notificationListPlaceholder -> @@ -515,7 +512,7 @@ object KeyguardRootViewBinder { viewModel.onNotificationContainerBoundsChanged( notificationListPlaceholder.top.toFloat(), notificationListPlaceholder.bottom.toFloat(), - animate = shouldAnimate + animate = shouldAnimate, ) } @@ -531,7 +528,7 @@ object KeyguardRootViewBinder { Int.MAX_VALUE } else { view.getTop() - } + }, ) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt index c6efcfad8da7..4cf3c4e7f6d0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt @@ -25,20 +25,18 @@ import androidx.constraintlayout.widget.ConstraintLayout import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config +import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -data class TransitionData( - val config: Config, - val start: Long = System.currentTimeMillis(), -) +data class TransitionData(val config: Config, val start: Long = System.currentTimeMillis()) class KeyguardBlueprintViewModel @Inject constructor( @Main private val handler: Handler, - keyguardBlueprintInteractor: KeyguardBlueprintInteractor, + private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor, ) { val blueprint = keyguardBlueprintInteractor.blueprint val blueprintId = keyguardBlueprintInteractor.blueprintId @@ -76,6 +74,9 @@ constructor( } } + fun refreshBlueprint(type: Type = Type.NoTransition) = + keyguardBlueprintInteractor.refreshBlueprint(type) + fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) { runningTransitions.mutate() @@ -95,7 +96,7 @@ constructor( Log.w( TAG, "runTransition: skipping ${transition::class.simpleName}: " + - "currentPriority=$currentPriority; config=$config" + "currentPriority=$currentPriority; config=$config", ) } apply() @@ -106,7 +107,7 @@ constructor( Log.i( TAG, "runTransition: running ${transition::class.simpleName}: " + - "currentPriority=$currentPriority; config=$config" + "currentPriority=$currentPriority; config=$config", ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index 73028c5cf496..36f684ee4759 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -25,10 +25,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.ui.SystemBarUtilsProxy @@ -56,10 +56,9 @@ constructor( var burnInLayer: Layer? = null val clockSize: StateFlow<ClockSize> = - combine( - keyguardClockInteractor.selectedClockSize, - keyguardClockInteractor.clockSize, - ) { selectedSize, clockSize -> + combine(keyguardClockInteractor.selectedClockSize, keyguardClockInteractor.clockSize) { + selectedSize, + clockSize -> if (selectedSize == ClockSizeSetting.SMALL) ClockSize.SMALL else clockSize } .stateIn( @@ -80,10 +79,7 @@ constructor( val currentClock = keyguardClockInteractor.currentClock val hasCustomWeatherDataDisplay = - combine( - isLargeClockVisible, - currentClock, - ) { isLargeClock, currentClock -> + combine(isLargeClockVisible, currentClock) { isLargeClock, currentClock -> currentClock?.let { clock -> val face = if (isLargeClock) clock.largeClock else clock.smallClock face.config.hasCustomWeatherDataDisplay @@ -93,14 +89,14 @@ constructor( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = - currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false + currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false, ) val clockShouldBeCentered: StateFlow<Boolean> = keyguardClockInteractor.clockShouldBeCentered.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false + initialValue = false, ) // To translate elements below smartspace in weather clock to avoid overlapping between date @@ -111,7 +107,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false + initialValue = false, ) val currentClockLayout: StateFlow<ClockLayout> = @@ -145,7 +141,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = ClockLayout.SMALL_CLOCK + initialValue = ClockLayout.SMALL_CLOCK, ) val hasCustomPositionUpdatedAnimation: StateFlow<Boolean> = @@ -156,7 +152,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false + initialValue = false, ) /** Calculates the top margin for the small clock. */ @@ -164,10 +160,10 @@ constructor( val statusBarHeight = systemBarUtils.getStatusBarHeaderHeightKeyguard() return if (shadeInteractor.isShadeLayoutWide.value) { resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) - - if (ComposeLockscreen.isEnabled) statusBarHeight else 0 + if (SceneContainerFlag.isEnabled) statusBarHeight else 0 } else { resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + - if (!ComposeLockscreen.isEnabled) statusBarHeight else 0 + if (!SceneContainerFlag.isEnabled) statusBarHeight else 0 } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 8505a784d302..49a8758ed51e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -123,7 +123,7 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) * Class that is responsible for keeping the view carousel up to date. This also handles changes in * state and applies them to the media carousel like the expansion. */ -@ExperimentalCoroutinesApi +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MediaCarouselController @Inject @@ -252,13 +252,13 @@ constructor( fun calculateAlpha( squishinessFraction: Float, startPosition: Float, - endPosition: Float + endPosition: Float, ): Float { val transformFraction = MathUtils.constrain( (squishinessFraction - startPosition) / (endPosition - startPosition), 0F, - 1F + 1F, ) return TRANSFORM_BEZIER.getInterpolation(transformFraction) } @@ -354,7 +354,7 @@ constructor( this::closeGuts, falsingManager, this::logSmartspaceImpression, - logger + logger, ) carouselLocale = context.resources.configuration.locales.get(0) isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL @@ -387,7 +387,7 @@ constructor( object : MediaHostStatesManager.Callback { override fun onHostStateChanged( @MediaLocation location: Int, - mediaHostState: MediaHostState + mediaHostState: MediaHostState, ) { updateUserVisibility() if (location == desiredLocation) { @@ -412,7 +412,7 @@ constructor( bgExecutor.execute { globalSettings.registerContentObserverSync( Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), - animationScaleObserver + animationScaleObserver, ) } } @@ -452,7 +452,7 @@ constructor( data: MediaData, immediately: Boolean, receivedSmartspaceCardLatency: Int, - isSsReactivated: Boolean + isSsReactivated: Boolean, ) { debugLogger.logMediaLoaded(key, data.active) if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) { @@ -468,7 +468,7 @@ constructor( SSPACE_CARD_REPORTED__LOCKSCREEN, SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), - rank = MediaPlayerData.getMediaPlayerIndex(key) + rank = MediaPlayerData.getMediaPlayerIndex(key), ) } if ( @@ -500,7 +500,7 @@ constructor( SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), rank = index, - receivedLatencyMillis = receivedSmartspaceCardLatency + receivedLatencyMillis = receivedSmartspaceCardLatency, ) } } @@ -533,7 +533,7 @@ constructor( override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, ) { debugLogger.logRecommendationLoaded(key, data.isActive) // Log the case where the hidden media carousel with the existed inactive resume @@ -568,7 +568,7 @@ constructor( receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) - .toInt() + .toInt(), ) } } @@ -589,7 +589,7 @@ constructor( receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) - .toInt() + .toInt(), ) } if ( @@ -644,10 +644,7 @@ constructor( mediaCarouselScrollHandler.onSettingsButtonUpdated(settings) settingsButton.setOnClickListener { logger.logCarouselSettings() - activityStarter.startActivity( - settingsIntent, - /* dismissShade= */ true, - ) + activityStarter.startActivity(settingsIntent, /* dismissShade= */ true) } } @@ -739,7 +736,7 @@ constructor( val lp = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) when (commonViewModel) { is MediaCommonViewModel.MediaControl -> { @@ -785,7 +782,7 @@ constructor( ) { mediaCarouselScrollHandler.scrollToPlayer( mediaCarouselScrollHandler.visibleMediaIndex, - destIndex = 0 + destIndex = 0, ) } mediaCarouselScrollHandler.onPlayersChanged() @@ -799,7 +796,7 @@ constructor( mediaCarouselScrollHandler.onPlayersChanged() onAddOrUpdateVisibleToUserCard( position, - commonViewModel is MediaCommonViewModel.MediaControl + commonViewModel is MediaCommonViewModel.MediaControl, ) } @@ -856,7 +853,7 @@ constructor( mediaCarouselScrollHandler.qsExpanded, mediaCarouselScrollHandler.visibleMediaIndex, currentEndLocation, - isMediaCardUpdate + isMediaCardUpdate, ) } } @@ -893,7 +890,7 @@ constructor( secureSettings.getBoolForUser( Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true, - UserHandle.USER_CURRENT + UserHandle.USER_CURRENT, ) } } @@ -926,7 +923,7 @@ constructor( private fun reorderAllPlayers( previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?, - key: String? = null + key: String? = null, ) { mediaContent.removeAllViews() for (mediaPlayer in MediaPlayerData.players()) { @@ -960,7 +957,7 @@ constructor( TAG, "Size of players list and number of views in carousel are out of sync. " + "Players size is ${MediaPlayerData.players().size}. " + - "View count is ${mediaContent.childCount}." + "View count is ${mediaContent.childCount}.", ) } } @@ -970,7 +967,7 @@ constructor( key: String, oldKey: String?, data: MediaData, - isSsReactivated: Boolean + isSsReactivated: Boolean, ): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") { MediaPlayerData.moveIfExists(oldKey, key) @@ -992,7 +989,7 @@ constructor( val lp = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) newPlayer.mediaViewHolder?.player?.setLayoutParams(lp) newPlayer.bindPlayer(data, key) @@ -1005,7 +1002,7 @@ constructor( newPlayer, systemClock, isSsReactivated, - debugLogger + debugLogger, ) updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true) // Media data added from a recommendation card should starts playing. @@ -1025,7 +1022,7 @@ constructor( existingPlayer, systemClock, isSsReactivated, - debugLogger + debugLogger, ) val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String() // In case of recommendations hits. @@ -1051,7 +1048,7 @@ constructor( private fun addSmartspaceMediaRecommendations( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, ) = traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") { if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel") @@ -1090,7 +1087,7 @@ constructor( val lp = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) newRecs.bindRecommendation(data) @@ -1117,7 +1114,7 @@ constructor( TAG, "Size of players list and number of views in carousel are out of sync. " + "Players size is ${MediaPlayerData.players().size}. " + - "View count is ${mediaContent.childCount}." + "View count is ${mediaContent.childCount}.", ) } } @@ -1173,7 +1170,7 @@ constructor( addSmartspaceMediaRecommendations( it.targetId, it, - MediaPlayerData.shouldPrioritizeSs + MediaPlayerData.shouldPrioritizeSs, ) } } else { @@ -1185,7 +1182,7 @@ constructor( key = key, oldKey = null, data = data, - isSsReactivated = isSsReactivated + isSsReactivated = isSsReactivated, ) } if (recreateMedia) { @@ -1248,7 +1245,7 @@ constructor( @MediaLocation startLocation: Int, @MediaLocation endLocation: Int, progress: Float, - immediately: Boolean + immediately: Boolean, ) { if ( startLocation != currentStartLocation || @@ -1286,7 +1283,7 @@ constructor( squishFraction, (pageIndicator.translationY + pageIndicator.height) / mediaCarousel.measuredHeight, - 1F + 1F, ) var alpha = 1.0f if (!endIsVisible || !startIsVisible) { @@ -1354,7 +1351,7 @@ constructor( currentCarouselHeight = height mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, - currentCarouselHeight + currentCarouselHeight, ) updatePageIndicatorLocation() updatePageIndicatorAlpha() @@ -1386,13 +1383,13 @@ constructor( private fun updateViewControllerToState( viewController: MediaViewController, - noAnimation: Boolean + noAnimation: Boolean, ) { viewController.setCurrentState( startLocation = currentStartLocation, endLocation = currentEndLocation, transitionProgress = currentTransitionProgress, - applyImmediately = noAnimation + applyImmediately = noAnimation, ) } @@ -1411,7 +1408,7 @@ constructor( desiredHostState: MediaHostState?, animate: Boolean, duration: Long = 200, - startDelay: Long = 0 + startDelay: Long = 0, ) = traceSection("MediaCarouselController#onDesiredLocationChanged") { desiredHostState?.let { @@ -1435,7 +1432,7 @@ constructor( if (animate) { mediaPlayer.mediaViewController.animatePendingStateChange( duration = duration, - delay = startDelay + delay = startDelay, ) } if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) { @@ -1506,7 +1503,7 @@ constructor( mediaCarouselViewModel.onCardVisibleToUser( qsExpanded, mediaCarouselScrollHandler.visibleMediaIndex, - currentEndLocation + currentEndLocation, ) return } @@ -1524,7 +1521,7 @@ constructor( 800, // SMARTSPACE_CARD_SEEN it.mSmartspaceId, it.mUid, - intArrayOf(it.surfaceForSmartspaceLogging) + intArrayOf(it.surfaceForSmartspaceLogging), ) it.mIsImpressed = true } @@ -1559,7 +1556,7 @@ constructor( interactedSubcardCardinality: Int = 0, rank: Int = mediaCarouselScrollHandler.visibleMediaIndex, receivedLatencyMillis: Int = 0, - isSwipeToDismiss: Boolean = false + isSwipeToDismiss: Boolean = false, ) { if (MediaPlayerData.players().size <= rank) { return @@ -1600,7 +1597,7 @@ constructor( interactedSubcardCardinality, receivedLatencyMillis, null, // Media cards cannot have subcards. - null // Media cards don't have dimensions today. + null, // Media cards don't have dimensions today. ) if (DEBUG) { @@ -1613,7 +1610,7 @@ constructor( "uid: $uid " + "interactedSubcardRank: $interactedSubcardRank " + "interactedSubcardCardinality: $interactedSubcardCardinality " + - "received_latency_millis: $receivedLatencyMillis" + "received_latency_millis: $receivedLatencyMillis", ) } } @@ -1633,7 +1630,7 @@ constructor( it.mUid, intArrayOf(it.surfaceForSmartspaceLogging), rank = index, - isSwipeToDismiss = true + isSwipeToDismiss = true, ) // Reset card impressed state when swipe to dismissed it.mIsImpressed = false @@ -1692,7 +1689,7 @@ internal object MediaPlayerData { active = true, resumeAction = null, instanceId = InstanceId.fakeInstanceId(-1), - appUid = -1 + appUid = -1, ) // Whether should prioritize Smartspace card. @@ -1741,7 +1738,7 @@ internal object MediaPlayerData { player: MediaControlPanel, clock: SystemClock, isSsReactivated: Boolean, - debugLogger: MediaCarouselControllerLogger? = null + debugLogger: MediaCarouselControllerLogger? = null, ) { val removedPlayer = removeMediaPlayer(key) if (removedPlayer != null && removedPlayer != player) { @@ -1754,7 +1751,7 @@ internal object MediaPlayerData { data, key, clock.currentTimeMillis(), - isSsReactivated = isSsReactivated + isSsReactivated = isSsReactivated, ) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) @@ -1768,7 +1765,7 @@ internal object MediaPlayerData { shouldPrioritize: Boolean, clock: SystemClock, debugLogger: MediaCarouselControllerLogger? = null, - update: Boolean = false + update: Boolean = false, ) { shouldPrioritizeSs = shouldPrioritize val removedPlayer = removeMediaPlayer(key) @@ -1782,7 +1779,7 @@ internal object MediaPlayerData { EMPTY.copy(active = data.isActive, isPlaying = false), key, clock.currentTimeMillis(), - isSsReactivated = true + isSsReactivated = true, ) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) @@ -1793,7 +1790,7 @@ internal object MediaPlayerData { fun moveIfExists( oldKey: String?, newKey: String, - debugLogger: MediaCarouselControllerLogger? = null + debugLogger: MediaCarouselControllerLogger? = null, ) { if (oldKey == null || oldKey == newKey) { return diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index ff9495daaa22..2961d05b2e51 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -57,7 +57,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { private static final float DEVICE_CONNECTED_ALPHA = 1f; protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>(); - public MediaOutputAdapter(MediaOutputController controller) { + public MediaOutputAdapter(MediaSwitchingController controller) { super(controller); setHasStableIds(true); } @@ -531,8 +531,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @RequiresApi(34) private static class Api34Impl { @DoNotInline - static View.OnClickListener getClickListenerBasedOnSelectionBehavior(MediaDevice device, - MediaOutputController controller, View.OnClickListener defaultTransferListener) { + static View.OnClickListener getClickListenerBasedOnSelectionBehavior( + MediaDevice device, + MediaSwitchingController controller, + View.OnClickListener defaultTransferListener) { switch (device.getSelectionBehavior()) { case SELECTION_BEHAVIOR_NONE: return null; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 5958b0a24a5e..63a7e013022a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -63,7 +63,7 @@ public abstract class MediaOutputBaseAdapter extends static final int CUSTOMIZED_ITEM_GROUP = 2; static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3; - protected final MediaOutputController mController; + protected final MediaSwitchingController mController; private static final int UNMUTE_DEFAULT_VOLUME = 2; @@ -73,7 +73,7 @@ public abstract class MediaOutputBaseAdapter extends int mCurrentActivePosition; private boolean mIsInitVolumeFirstTime; - public MediaOutputBaseAdapter(MediaOutputController controller) { + public MediaOutputBaseAdapter(MediaSwitchingController controller) { mController = controller; mIsDragging = false; mCurrentActivePosition = -1; @@ -127,7 +127,7 @@ public abstract class MediaOutputBaseAdapter extends return mCurrentActivePosition; } - public MediaOutputController getController() { + public MediaSwitchingController getController() { return mController; } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 6cc4dcbaa1ea..6bc995f3437c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -65,11 +65,9 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -/** - * Base dialog for media output UI - */ -public abstract class MediaOutputBaseDialog extends SystemUIDialog implements - MediaOutputController.Callback, Window.Callback { +/** Base dialog for media output UI */ +public abstract class MediaOutputBaseDialog extends SystemUIDialog + implements MediaSwitchingController.Callback, Window.Callback { private static final String TAG = "MediaOutputDialog"; private static final String EMPTY_TITLE = " "; @@ -82,7 +80,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements private final RecyclerView.LayoutManager mLayoutManager; final Context mContext; - final MediaOutputController mMediaOutputController; + final MediaSwitchingController mMediaSwitchingController; final BroadcastSender mBroadcastSender; /** @@ -212,22 +210,22 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements @Override public void onLayoutCompleted(RecyclerView.State state) { super.onLayoutCompleted(state); - mMediaOutputController.setRefreshing(false); - mMediaOutputController.refreshDataSetIfNeeded(); + mMediaSwitchingController.setRefreshing(false); + mMediaSwitchingController.refreshDataSetIfNeeded(); } } public MediaOutputBaseDialog( Context context, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController, + MediaSwitchingController mediaSwitchingController, boolean includePlaybackAndAppMetadata) { super(context, R.style.Theme_SystemUI_Dialog_Media); // Save the context that is wrapped with our theme. mContext = getContext(); mBroadcastSender = broadcastSender; - mMediaOutputController = mediaOutputController; + mMediaSwitchingController = mediaSwitchingController; mLayoutManager = new LayoutManagerWrapper(mContext); mListMaxHeight = context.getResources().getDimensionPixelSize( R.dimen.media_output_dialog_list_max_height); @@ -279,9 +277,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements // Init bottom buttons mDoneButton.setOnClickListener(v -> dismiss()); mStopButton.setOnClickListener(v -> onStopButtonClick()); - mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication); + mAppButton.setOnClickListener(mMediaSwitchingController::tryToLaunchMediaApplication); mMediaMetadataSectionLayout.setOnClickListener( - mMediaOutputController::tryToLaunchMediaApplication); + mMediaSwitchingController::tryToLaunchMediaApplication); mDismissing = false; } @@ -298,10 +296,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements @Override public void start() { - mMediaOutputController.start(this); + mMediaSwitchingController.start(this); if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) { - mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor, - mBroadcastCallback); + mMediaSwitchingController.registerLeBroadcastServiceCallback( + mExecutor, mBroadcastCallback); mIsLeBroadcastCallbackRegistered = true; } } @@ -311,11 +309,11 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements // unregister broadcast callback should only depend on profile and registered flag // rather than remote device or broadcast state // otherwise it might have risks of leaking registered callback handle - if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) { - mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback); + if (mMediaSwitchingController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) { + mMediaSwitchingController.unregisterLeBroadcastServiceCallback(mBroadcastCallback); mIsLeBroadcastCallbackRegistered = false; } - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); } @VisibleForTesting @@ -326,18 +324,17 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements void refresh(boolean deviceSetChanged) { // TODO(287191450): remove binder calls in this method from the UI thread. // If the dialog is going away or is already refreshing, do nothing. - if (mDismissing || mMediaOutputController.isRefreshing()) { + if (mDismissing || mMediaSwitchingController.isRefreshing()) { return; } - mMediaOutputController.setRefreshing(true); + mMediaSwitchingController.setRefreshing(true); // Update header icon final int iconRes = getHeaderIconRes(); final IconCompat headerIcon = getHeaderIcon(); final IconCompat appSourceIcon = getAppSourceIcon(); boolean colorSetUpdated = false; mCastAppLayout.setVisibility( - mMediaOutputController.shouldShowLaunchSection() - ? View.VISIBLE : View.GONE); + mMediaSwitchingController.shouldShowLaunchSection() ? View.VISIBLE : View.GONE); if (iconRes != 0) { mHeaderIcon.setVisibility(View.VISIBLE); mHeaderIcon.setImageResource(iconRes); @@ -371,10 +368,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mAppResourceIcon.setVisibility(View.GONE); } else if (appSourceIcon != null) { Icon appIcon = appSourceIcon.toIcon(mContext); - mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent()); + mAppResourceIcon.setColorFilter(mMediaSwitchingController.getColorItemContent()); mAppResourceIcon.setImageIcon(appIcon); } else { - Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage(); + Drawable appIconDrawable = mMediaSwitchingController.getAppSourceIconFromPackage(); if (appIconDrawable != null) { mAppResourceIcon.setImageDrawable(appIconDrawable); } else { @@ -387,7 +384,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements R.dimen.media_output_dialog_header_icon_padding); mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); } - mAppButton.setText(mMediaOutputController.getAppSourceName()); + mAppButton.setText(mMediaSwitchingController.getAppSourceName()); if (!mIncludePlaybackAndAppMetadata) { mHeaderTitle.setVisibility(View.GONE); @@ -424,23 +421,26 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mAdapter.updateItems(); } } else { - mMediaOutputController.setRefreshing(false); - mMediaOutputController.refreshDataSetIfNeeded(); + mMediaSwitchingController.setRefreshing(false); + mMediaSwitchingController.refreshDataSetIfNeeded(); } } private void updateButtonBackgroundColorFilter() { - ColorFilter buttonColorFilter = new PorterDuffColorFilter( - mMediaOutputController.getColorButtonBackground(), - PorterDuff.Mode.SRC_IN); + ColorFilter buttonColorFilter = + new PorterDuffColorFilter( + mMediaSwitchingController.getColorButtonBackground(), + PorterDuff.Mode.SRC_IN); mDoneButton.getBackground().setColorFilter(buttonColorFilter); mStopButton.getBackground().setColorFilter(buttonColorFilter); - mDoneButton.setTextColor(mMediaOutputController.getColorPositiveButtonText()); + mDoneButton.setTextColor(mMediaSwitchingController.getColorPositiveButtonText()); } private void updateDialogBackgroundColor() { - getDialogView().getBackground().setTint(mMediaOutputController.getColorDialogBackground()); - mDeviceListLayout.setBackgroundColor(mMediaOutputController.getColorDialogBackground()); + getDialogView() + .getBackground() + .setTint(mMediaSwitchingController.getColorDialogBackground()); + mDeviceListLayout.setBackgroundColor(mMediaSwitchingController.getColorDialogBackground()); } private Drawable resizeDrawable(Drawable drawable, int size) { @@ -499,7 +499,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements protected void startLeBroadcast() { mStopButton.setText(R.string.media_output_broadcast_starting); mStopButton.setEnabled(false); - if (!mMediaOutputController.startBluetoothLeBroadcast()) { + if (!mMediaSwitchingController.startBluetoothLeBroadcast()) { // If the system can't execute "broadcast start", then UI shows the error. handleLeBroadcastStartFailed(); } @@ -512,9 +512,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements && sharedPref.getBoolean(PREF_IS_LE_BROADCAST_FIRST_LAUNCH, true)) { Log.d(TAG, "PREF_IS_LE_BROADCAST_FIRST_LAUNCH: true"); - mMediaOutputController.launchLeBroadcastNotifyDialog(mDialogView, + mMediaSwitchingController.launchLeBroadcastNotifyDialog( + mDialogView, mBroadcastSender, - MediaOutputController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH, + MediaSwitchingController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH, (d, w) -> { startLeBroadcast(); }); @@ -527,14 +528,13 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } protected void startLeBroadcastDialog() { - mMediaOutputController.launchMediaOutputBroadcastDialog(mDialogView, - mBroadcastSender); + mMediaSwitchingController.launchMediaOutputBroadcastDialog(mDialogView, mBroadcastSender); refresh(); } protected void stopLeBroadcast() { mStopButton.setEnabled(false); - if (!mMediaOutputController.stopBluetoothLeBroadcast()) { + if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) { // If the system can't execute "broadcast stop", then UI does refresh. mMainThreadHandler.post(() -> refresh()); } @@ -559,7 +559,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } public void onStopButtonClick() { - mMediaOutputController.releaseSession(); + mMediaSwitchingController.releaseSession(); dismiss(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index 1e317554859c..9b5b872a00db 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -235,14 +235,17 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } }; - MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar, - BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { + MediaOutputBroadcastDialog( + Context context, + boolean aboveStatusbar, + BroadcastSender broadcastSender, + MediaSwitchingController mediaSwitchingController) { super( context, broadcastSender, - mediaOutputController, /* includePlaybackAndAppMetadata */ + mediaSwitchingController, /* includePlaybackAndAppMetadata */ true); - mAdapter = new MediaOutputAdapter(mMediaOutputController); + mAdapter = new MediaOutputAdapter(mMediaSwitchingController); // TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class // that extends MediaOutputBaseDialog if (!aboveStatusbar) { @@ -262,8 +265,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { super.start(); if (!mIsLeBroadcastAssistantCallbackRegistered) { mIsLeBroadcastAssistantCallbackRegistered = true; - mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor, - mBroadcastAssistantCallback); + mMediaSwitchingController.registerLeBroadcastAssistantServiceCallback( + mExecutor, mBroadcastAssistantCallback); } /* Add local source broadcast to connected capable devices that may be possible receivers * of stream. @@ -276,7 +279,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { super.stop(); if (mIsLeBroadcastAssistantCallbackRegistered) { mIsLeBroadcastAssistantCallbackRegistered = false; - mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback( + mMediaSwitchingController.unregisterLeBroadcastAssistantServiceCallback( mBroadcastAssistantCallback); } } @@ -288,7 +291,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override IconCompat getHeaderIcon() { - return mMediaOutputController.getHeaderIcon(); + return mMediaSwitchingController.getHeaderIcon(); } @Override @@ -299,17 +302,17 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override CharSequence getHeaderText() { - return mMediaOutputController.getHeaderTitle(); + return mMediaSwitchingController.getHeaderTitle(); } @Override CharSequence getHeaderSubtitle() { - return mMediaOutputController.getHeaderSubTitle(); + return mMediaSwitchingController.getHeaderSubTitle(); } @Override IconCompat getAppSourceIcon() { - return mMediaOutputController.getNotificationSmallIcon(); + return mMediaSwitchingController.getNotificationSmallIcon(); } @Override @@ -319,16 +322,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void onStopButtonClick() { - mMediaOutputController.stopBluetoothLeBroadcast(); + mMediaSwitchingController.stopBluetoothLeBroadcast(); dismiss(); } private String getBroadcastMetadataInfo(int metadata) { switch (metadata) { case METADATA_BROADCAST_NAME: - return mMediaOutputController.getBroadcastName(); + return mMediaSwitchingController.getBroadcastName(); case METADATA_BROADCAST_CODE: - return mMediaOutputController.getBroadcastCode(); + return mMediaSwitchingController.getBroadcastCode(); default: return ""; } @@ -342,13 +345,15 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { mBroadcastQrCodeView = getDialogView().requireViewById(R.id.qrcode_view); mBroadcastNotify = getDialogView().requireViewById(R.id.broadcast_info); - mBroadcastNotify.setOnClickListener(v -> { - mMediaOutputController.launchLeBroadcastNotifyDialog( - /* view= */ null, - /* broadcastSender= */ null, - MediaOutputController.BroadcastNotifyDialog.ACTION_BROADCAST_INFO_ICON, - /* onClickListener= */ null); - }); + mBroadcastNotify.setOnClickListener( + v -> { + mMediaSwitchingController.launchLeBroadcastNotifyDialog( + /* mediaOutputDialog= */ null, + /* broadcastSender= */ null, + MediaSwitchingController.BroadcastNotifyDialog + .ACTION_BROADCAST_INFO_ICON, + /* listener= */ null); + }); mBroadcastName = getDialogView().requireViewById(R.id.broadcast_name_summary); mBroadcastNameEdit = getDialogView().requireViewById(R.id.broadcast_name_edit); mBroadcastNameEdit.setOnClickListener(v -> { @@ -409,16 +414,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { return; } - for (BluetoothDevice sink : mMediaOutputController.getConnectedBroadcastSinkDevices()) { + for (BluetoothDevice sink : mMediaSwitchingController.getConnectedBroadcastSinkDevices()) { Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId() + ", the device: " + sink.getAnonymizedAddress()); - if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) { + if (mMediaSwitchingController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) { Log.d(TAG, "The sink device has the broadcast source now."); return; } - if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink, - broadcastMetadata, /*isGroupOp=*/ false)) { + if (!mMediaSwitchingController.addSourceIntoSinkDeviceWithBluetoothLeAssistant( + sink, broadcastMetadata, /* isGroupOp= */ false)) { Log.e(TAG, "Error: Source add failed"); } } @@ -457,11 +462,11 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } private String getLocalBroadcastMetadataQrCodeString() { - return mMediaOutputController.getLocalBroadcastMetadataQrCodeString(); + return mMediaSwitchingController.getLocalBroadcastMetadataQrCodeString(); } private BluetoothLeBroadcastMetadata getBroadcastMetadata() { - return mMediaOutputController.getBroadcastMetadata(); + return mMediaSwitchingController.getBroadcastMetadata(); } @VisibleForTesting @@ -476,8 +481,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { * stopped then used the new Broadcast code to start the Broadcast. */ mIsStopbyUpdateBroadcastCode = true; - mMediaOutputController.setBroadcastCode(updatedString); - if (!mMediaOutputController.stopBluetoothLeBroadcast()) { + mMediaSwitchingController.setBroadcastCode(updatedString); + if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) { handleLeBroadcastStopFailed(); return; } @@ -485,8 +490,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { /* If the user wants to update the Broadcast Name, we don't need to stop the Broadcast * session. Only use the new Broadcast name to update the broadcast session. */ - mMediaOutputController.setBroadcastName(updatedString); - if (!mMediaOutputController.updateBluetoothLeBroadcast()) { + mMediaSwitchingController.setBroadcastName(updatedString); + if (!mMediaSwitchingController.updateBluetoothLeBroadcast()) { handleLeBroadcastUpdateFailed(); } } @@ -496,12 +501,13 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { public boolean isBroadcastSupported() { if (!legacyLeAudioSharing()) return false; boolean isBluetoothLeDevice = false; - if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) { - isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice( - mMediaOutputController.getCurrentConnectedMediaDevice()); + if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) { + isBluetoothLeDevice = + mMediaSwitchingController.isBluetoothLeDevice( + mMediaSwitchingController.getCurrentConnectedMediaDevice()); } - return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice; + return mMediaSwitchingController.isBroadcastSupported() && isBluetoothLeDevice; } @Override @@ -515,7 +521,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void handleLeBroadcastStartFailed() { - mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode); + mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode); mRetryCount++; handleUpdateFailedUi(); @@ -538,8 +544,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void handleLeBroadcastUpdateFailed() { - //Change the value in shared preferences back to it original value - mMediaOutputController.setBroadcastName(mCurrentBroadcastName); + // Change the value in shared preferences back to it original value + mMediaSwitchingController.setBroadcastName(mCurrentBroadcastName); mRetryCount++; handleUpdateFailedUi(); @@ -550,7 +556,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { if (mIsStopbyUpdateBroadcastCode) { mIsStopbyUpdateBroadcastCode = false; mRetryCount = 0; - if (!mMediaOutputController.startBluetoothLeBroadcast()) { + if (!mMediaSwitchingController.startBluetoothLeBroadcast()) { handleLeBroadcastStartFailed(); return; } @@ -561,8 +567,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void handleLeBroadcastStopFailed() { - //Change the value in shared preferences back to it original value - mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode); + // Change the value in shared preferences back to it original value + mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode); mRetryCount++; handleUpdateFailedUi(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt index 6ef9ea36882b..2e7e66f5b384 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt @@ -29,7 +29,7 @@ constructor( private val context: Context, private val broadcastSender: BroadcastSender, private val dialogTransitionAnimator: DialogTransitionAnimator, - private val mediaOutputControllerFactory: MediaOutputController.Factory + private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory ) { var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null @@ -41,7 +41,7 @@ constructor( // TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to // disambiguate the same package running on different users. val controller = - mediaOutputControllerFactory.create( + mediaSwitchingControllerFactory.create( packageName, /* userHandle= */ null, /* token */ null, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index eb6a32023eb7..c9af7b322811 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -46,14 +46,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController, + MediaSwitchingController mediaSwitchingController, DialogTransitionAnimator dialogTransitionAnimator, UiEventLogger uiEventLogger, boolean includePlaybackAndAppMetadata) { - super(context, broadcastSender, mediaOutputController, includePlaybackAndAppMetadata); + super(context, broadcastSender, mediaSwitchingController, includePlaybackAndAppMetadata); mDialogTransitionAnimator = dialogTransitionAnimator; mUiEventLogger = uiEventLogger; - mAdapter = new MediaOutputAdapter(mMediaOutputController); + mAdapter = new MediaOutputAdapter(mMediaSwitchingController); if (!aboveStatusbar) { getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } @@ -72,7 +72,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override IconCompat getHeaderIcon() { - return mMediaOutputController.getHeaderIcon(); + return mMediaSwitchingController.getHeaderIcon(); } @Override @@ -83,27 +83,29 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override CharSequence getHeaderText() { - return mMediaOutputController.getHeaderTitle(); + return mMediaSwitchingController.getHeaderTitle(); } @Override CharSequence getHeaderSubtitle() { - return mMediaOutputController.getHeaderSubTitle(); + return mMediaSwitchingController.getHeaderSubTitle(); } @Override IconCompat getAppSourceIcon() { - return mMediaOutputController.getNotificationSmallIcon(); + return mMediaSwitchingController.getNotificationSmallIcon(); } @Override int getStopButtonVisibility() { boolean isActiveRemoteDevice = false; - if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) { - isActiveRemoteDevice = mMediaOutputController.isActiveRemoteDevice( - mMediaOutputController.getCurrentConnectedMediaDevice()); + if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) { + isActiveRemoteDevice = + mMediaSwitchingController.isActiveRemoteDevice( + mMediaSwitchingController.getCurrentConnectedMediaDevice()); } - boolean showBroadcastButton = isBroadcastSupported() && mMediaOutputController.isPlaying(); + boolean showBroadcastButton = + isBroadcastSupported() && mMediaSwitchingController.isPlaying(); return (isActiveRemoteDevice || showBroadcastButton) ? View.VISIBLE : View.GONE; } @@ -115,13 +117,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { boolean isBroadcastEnabled = false; if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) { - if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) { - isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice( - mMediaOutputController.getCurrentConnectedMediaDevice()); + if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) { + isBluetoothLeDevice = + mMediaSwitchingController.isBluetoothLeDevice( + mMediaSwitchingController.getCurrentConnectedMediaDevice()); // if broadcast is active, broadcast should be considered as supported // there could be a valid case that broadcast is ongoing // without active LEA device connected - isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled(); + isBroadcastEnabled = mMediaSwitchingController.isBluetoothLeBroadcastEnabled(); } } else { // To decouple LE Audio Broadcast and Unicast, it always displays the button when there @@ -129,15 +132,16 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { isBluetoothLeDevice = true; } - return mMediaOutputController.isBroadcastSupported() + return mMediaSwitchingController.isBroadcastSupported() && (isBluetoothLeDevice || isBroadcastEnabled); } @Override public CharSequence getStopButtonText() { int resId = R.string.media_output_dialog_button_stop_casting; - if (isBroadcastSupported() && mMediaOutputController.isPlaying() - && !mMediaOutputController.isBluetoothLeBroadcastEnabled()) { + if (isBroadcastSupported() + && mMediaSwitchingController.isPlaying() + && !mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) { resId = R.string.media_output_broadcast; } return mContext.getText(resId); @@ -145,8 +149,8 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override public void onStopButtonClick() { - if (isBroadcastSupported() && mMediaOutputController.isPlaying()) { - if (!mMediaOutputController.isBluetoothLeBroadcastEnabled()) { + if (isBroadcastSupported() && mMediaSwitchingController.isPlaying()) { + if (!mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) { if (startLeBroadcastDialogForFirstTime()) { return; } @@ -155,7 +159,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { stopLeBroadcast(); } } else { - mMediaOutputController.releaseSession(); + mMediaSwitchingController.releaseSession(); mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations(); dismiss(); } @@ -163,8 +167,9 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override public int getBroadcastIconVisibility() { - return (isBroadcastSupported() && mMediaOutputController.isBluetoothLeBroadcastEnabled()) - ? View.VISIBLE : View.GONE; + return (isBroadcastSupported() && mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) + ? View.VISIBLE + : View.GONE; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt index 47e069102035..4e9451a838ad 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt @@ -35,7 +35,7 @@ constructor( private val broadcastSender: BroadcastSender, private val uiEventLogger: UiEventLogger, private val dialogTransitionAnimator: DialogTransitionAnimator, - private val mediaOutputControllerFactory: MediaOutputController.Factory, + private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory, ) { companion object { const val INTERACTION_JANK_TAG = "media_output" @@ -118,7 +118,7 @@ constructor( // Dismiss the previous dialog, if any. mediaOutputDialog?.dismiss() - val controller = mediaOutputControllerFactory.create(packageName, userHandle, token) + val controller = mediaSwitchingControllerFactory.create(packageName, userHandle, token) val mediaOutputDialog = MediaOutputDialog( diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java index 875e505db1c6..2cbc75755cfd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -77,6 +77,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.InfoMediaManager; +import com.android.settingslib.media.InputRouteManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.flags.Flags; @@ -116,12 +117,13 @@ import java.util.function.Function; import java.util.stream.Collectors; /** - * Controller for media output dialog + * Controller for a dialog that allows users to switch media output and input devices, control + * volume, connect to new devices, etc. */ -public class MediaOutputController implements LocalMediaManager.DeviceCallback, - INearbyMediaDevicesUpdateCallback { +public class MediaSwitchingController + implements LocalMediaManager.DeviceCallback, INearbyMediaDevicesUpdateCallback { - private static final String TAG = "MediaOutputController"; + private static final String TAG = "MediaSwitchingController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String PAGE_CONNECTED_DEVICES_KEY = "top_level_connected_devices"; @@ -137,10 +139,12 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private final DialogTransitionAnimator mDialogTransitionAnimator; private final CommonNotifCollection mNotifCollection; protected final Object mMediaDevicesLock = new Object(); + protected final Object mInputMediaDevicesLock = new Object(); @VisibleForTesting final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>(); - private final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>(); + private final List<MediaItem> mOutputMediaItemList = new CopyOnWriteArrayList<>(); + private final List<MediaItem> mInputMediaItemList = new CopyOnWriteArrayList<>(); private final AudioManager mAudioManager; private final PowerExemptionManager mPowerExemptionManager; private final KeyguardManager mKeyGuardManager; @@ -153,6 +157,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @VisibleForTesting boolean mNeedRefresh = false; private MediaController mMediaController; + private InputRouteManager mInputRouteManager; @VisibleForTesting Callback mCallback; @VisibleForTesting @@ -181,8 +186,20 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, ACTION_BROADCAST_INFO_ICON } + @VisibleForTesting + final InputRouteManager.InputDeviceCallback mInputDeviceCallback = + new InputRouteManager.InputDeviceCallback() { + @Override + public void onInputDeviceListUpdated(@NonNull List<MediaDevice> devices) { + synchronized (mInputMediaDevicesLock) { + buildInputMediaItems(devices); + mCallback.onDeviceListChanged(); + } + } + }; + @AssistedInject - public MediaOutputController( + public MediaSwitchingController( Context context, @Assisted String packageName, @Assisted @Nullable UserHandle userHandle, @@ -241,19 +258,23 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, R.dimen.media_output_dialog_default_margin_end); mItemMarginEndSelectable = (int) mContext.getResources().getDimension( R.dimen.media_output_dialog_selectable_margin_end); + + if (enableInputRouting()) { + mInputRouteManager = new InputRouteManager(mContext, audioManager); + } } @AssistedFactory public interface Factory { - /** Construct a MediaOutputController */ - MediaOutputController create( + /** Construct a MediaSwitchingController */ + MediaSwitchingController create( String packageName, UserHandle userHandle, MediaSession.Token token); } protected void start(@NonNull Callback cb) { synchronized (mMediaDevicesLock) { mCachedMediaDevices.clear(); - mMediaItemList.clear(); + mOutputMediaItemList.clear(); } mNearbyDeviceInfoMap.clear(); if (mNearbyMediaDevicesManager != null) { @@ -277,6 +298,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mCallback = cb; mLocalMediaManager.registerCallback(this); mLocalMediaManager.startScan(); + + if (enableInputRouting()) { + mInputRouteManager.registerCallback(mInputDeviceCallback); + } } boolean shouldShowLaunchSection() { @@ -300,12 +325,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mLocalMediaManager.stopScan(); synchronized (mMediaDevicesLock) { mCachedMediaDevices.clear(); - mMediaItemList.clear(); + mOutputMediaItemList.clear(); } if (mNearbyMediaDevicesManager != null) { mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this); } mNearbyDeviceInfoMap.clear(); + + if (enableInputRouting()) { + mInputRouteManager.unregisterCallback(mInputDeviceCallback); + synchronized (mInputMediaDevicesLock) { + mInputMediaItemList.clear(); + } + } } private MediaController getMediaController() { @@ -335,7 +367,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Override public void onDeviceListUpdate(List<MediaDevice> devices) { - boolean isListEmpty = mMediaItemList.isEmpty(); + boolean isListEmpty = mOutputMediaItemList.isEmpty(); if (isListEmpty || !mIsRefreshing) { buildMediaItems(devices); mCallback.onDeviceListChanged(); @@ -352,7 +384,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, public void onSelectedDeviceStateChanged(MediaDevice device, @LocalMediaManager.MediaDeviceState int state) { mCallback.onRouteChanged(); - mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList)); + mMetricLogger.logOutputItemSuccess( + device.toString(), new ArrayList<>(mOutputMediaItemList)); } @Override @@ -363,7 +396,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Override public void onRequestFailed(int reason) { mCallback.onRouteChanged(); - mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason); + mMetricLogger.logOutputItemFailure(new ArrayList<>(mOutputMediaItemList), reason); } /** @@ -382,7 +415,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } try { synchronized (mMediaDevicesLock) { - mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); + mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); } mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice()); } catch (Exception e) { @@ -638,9 +671,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private void buildMediaItems(List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { - List<MediaItem> updatedMediaItems = buildMediaItems(mMediaItemList, devices); - mMediaItemList.clear(); - mMediaItemList.addAll(updatedMediaItems); + List<MediaItem> updatedMediaItems = buildMediaItems(mOutputMediaItemList, devices); + mOutputMediaItemList.clear(); + mOutputMediaItemList.addAll(updatedMediaItems); } } @@ -714,6 +747,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } } + private boolean enableInputRouting() { + return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl(); + } + + private void buildInputMediaItems(List<MediaDevice> devices) { + synchronized (mInputMediaDevicesLock) { + List<MediaItem> updatedInputMediaItems = + devices.stream().map(MediaItem::createDeviceMediaItem).toList(); + mInputMediaItemList.clear(); + mInputMediaItemList.addAll(updatedInputMediaItems); + } + } + /** * Initial categorization of current devices, will not be called for updates to the devices * list. @@ -778,7 +824,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId())); } } - } boolean isCurrentConnectedDeviceRemote() { @@ -837,8 +882,31 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, }); } + private void addInputDevices(List<MediaItem> mediaItems) { + mediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString(R.string.media_input_group_title))); + mediaItems.addAll(mInputMediaItemList); + } + + private void addOutputDevices(List<MediaItem> mediaItems) { + mediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString(R.string.media_output_group_title))); + mediaItems.addAll(mOutputMediaItemList); + } + public List<MediaItem> getMediaItemList() { - return mMediaItemList; + // If input routing is not enabled, only return output media items. + if (!enableInputRouting()) { + return mOutputMediaItemList; + } + + // If input routing is enabled, return both output and input media items. + List<MediaItem> mediaItems = new ArrayList<>(); + addOutputDevices(mediaItems); + addInputDevices(mediaItems); + return mediaItems; } public MediaDevice getCurrentConnectedMediaDevice() { @@ -921,7 +989,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, public boolean isAnyDeviceTransferring() { synchronized (mMediaDevicesLock) { - for (MediaItem mediaItem : mMediaItemList) { + for (MediaItem mediaItem : mOutputMediaItemList) { if (mediaItem.getMediaDevice().isPresent() && mediaItem.getMediaDevice().get().getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { @@ -986,8 +1054,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) { - MediaOutputController controller = - new MediaOutputController( + MediaSwitchingController controller = + new MediaSwitchingController( mContext, mPackageName, mUserHandle, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index aa6c08eecd76..45aad825a70f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -140,6 +140,7 @@ fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) { } }, onLongClick = { tile.onLongClick(expandable) }, + accessibilityUiState = uiState.accessibilityUiState, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index e11ffccb0be3..b7e2cf23e3a8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -61,6 +61,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.session.shared.SessionStorage import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.logger.SceneLogger +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.NotificationShadeWindowController @@ -228,8 +229,10 @@ constructor( is ObservableTransitionState.Idle -> { if (state.currentScene != Scenes.Gone) { true to "scene is not Gone" + } else if (state.currentOverlays.isNotEmpty()) { + true to "overlay is shown" } else { - false to "scene is Gone" + false to "scene is Gone and no overlays are shown" } } is ObservableTransitionState.Transition -> { @@ -712,19 +715,21 @@ constructor( if (isDeviceLocked) { sceneInteractor.transitionState .mapNotNull { it as? ObservableTransitionState.Idle } - .map { it.currentScene } + .map { it.currentScene to it.currentOverlays } .distinctUntilChanged() - .map { sceneKey -> - when (sceneKey) { + .map { (sceneKey, currentOverlays) -> + when { // When locked, showing the lockscreen scene should be reported // as "interacting" while showing other scenes should report as // "not interacting". // // This is done here in order to match the legacy // implementation. The real reason why is lost to lore and myth. - Scenes.Lockscreen -> true - Scenes.Bouncer -> false - Scenes.Shade -> false + Overlays.NotificationsShade in currentOverlays -> false + Overlays.QuickSettingsShade in currentOverlays -> null + sceneKey == Scenes.Lockscreen -> true + sceneKey == Scenes.Bouncer -> false + sceneKey == Scenes.Shade -> false else -> null } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index 751448fe607e..7b6b0f614cc2 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -26,7 +26,6 @@ import com.android.systemui.flags.RefactorFlagUtils import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag @@ -39,7 +38,6 @@ object SceneContainerFlag { inline val isEnabled get() = sceneContainer() && // mainAconfigFlag - ComposeLockscreen.isEnabled && KeyguardBottomAreaRefactor.isEnabled && KeyguardWmStateRefactor.isEnabled && MigrateClocksToBlueprint.isEnabled && @@ -55,7 +53,6 @@ object SceneContainerFlag { /** The set of secondary flags which must be enabled for scene container to work properly */ inline fun getSecondaryFlags(): Sequence<FlagToken> = sequenceOf( - ComposeLockscreen.token, KeyguardBottomAreaRefactor.token, KeyguardWmStateRefactor.token, MigrateClocksToBlueprint.token, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 42499f043457..f76c5fd4ca83 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -137,7 +137,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; -import com.android.systemui.keyguard.shared.ComposeLockscreen; import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -186,6 +185,7 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; @@ -207,7 +207,6 @@ import com.android.systemui.statusbar.phone.BounceInterpolator; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -2511,11 +2510,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return 0; } - if (ComposeLockscreen.isEnabled()) { - return (int) mKeyguardInteractor.getNotificationContainerBounds() - .getValue().getTop(); - } - if (!mKeyguardBypassController.getBypassEnabled()) { if (MigrateClocksToBlueprint.isEnabled() && !mSplitShadeEnabled) { return (int) mKeyguardInteractor.getNotificationContainerBounds() diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 3f3ad13f9b12..4f47536f6b32 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; import android.os.Build; @@ -987,6 +988,19 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void onConfigChanged(Configuration newConfig) { + // If the shade window is not visible, the bounds will not update until it becomes visible. + // Touches that should invoke shade expansion but are not within those incorrect bounds + // (because the shape of the shade window remains portrait after flipping to landscape) will + // be dropped, causing the shade expansion to fail silently. Since the shade doesn't open, + // it doesn't become visible, and the bounds will never update. Therefore, we must detect + // the incorrect bounds here and force the update so that touches are routed correctly. + if (SceneContainerFlag.isEnabled() && mWindowRootView.getVisibility() == View.INVISIBLE) { + Rect bounds = newConfig.windowConfiguration.getBounds(); + if (mWindowRootView.getWidth() != bounds.width()) { + mLogger.logConfigChangeWidthAdjust(mWindowRootView.getWidth(), bounds.width()); + updateRootViewBounds(bounds); + } + } final boolean newScreenRotationAllowed = mKeyguardStateController .isKeyguardScreenRotationAllowed(); @@ -996,6 +1010,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } + private void updateRootViewBounds(Rect bounds) { + int originalMlpWidth = mLp.width; + int originalMlpHeight = mLp.height; + mLp.width = bounds.width(); + mLp.height = bounds.height(); + mWindowManager.updateViewLayout(mWindowRootView, mLp); + mLp.width = originalMlpWidth; + mLp.height = originalMlpHeight; + } + /** * When keyguard will be dismissed but didn't start animation yet. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt index e7a397b0fa09..1693e62c89fb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt @@ -31,18 +31,13 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { fun logNewState(state: Any) { - buffer.log( - TAG, - DEBUG, - { str1 = state.toString() }, - { "Applying new state: $str1" } - ) + buffer.log(TAG, DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" }) } private inline fun log( logLevel: LogLevel, initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String + noinline printer: LogMessage.() -> String, ) { buffer.log(TAG, logLevel, initializer, printer) } @@ -52,7 +47,8 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = visible }, - { "Updating visibility, should be visible : $bool1" }) + { "Updating visibility, should be visible : $bool1" }, + ) } fun logIsExpanded( @@ -65,7 +61,7 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: headsUpNotificationShowing: Boolean, scrimsVisibilityNotTransparent: Boolean, backgroundBlurRadius: Boolean, - launchingActivityFromNotification: Boolean + launchingActivityFromNotification: Boolean, ) { buffer.log( TAG, @@ -82,11 +78,13 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: long2 = if (backgroundBlurRadius) 1 else 0 double1 = if (launchingActivityFromNotification) 1.0 else 0.0 }, - { "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " + + { + "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " + "isKeyguardShowingAndNotOccluded $bool2, panelVisible $bool3, " + "keyguardFadingAway $bool4, bouncerShowing $int1," + "headsUpNotificationShowing $int2, scrimsVisibilityNotTransparent $long1," + - "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"} + "backgroundBlurRadius $long2, launchingActivityFromNotification $double1" + }, ) } @@ -95,7 +93,7 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = visible }, - { "Updating shade, should be visible and focusable: $bool1" } + { "Updating shade, should be visible and focusable: $bool1" }, ) } @@ -104,7 +102,19 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = focusable }, - { "Updating shade, should be focusable : $bool1" } + { "Updating shade, should be focusable : $bool1" }, + ) + } + + fun logConfigChangeWidthAdjust(originalWidth: Int, newWidth: Int) { + buffer.log( + TAG, + DEBUG, + { + int1 = originalWidth + int2 = newWidth + }, + { "Config changed. SceneWindowRootView width updating from $int1 to $int2." }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index 4056e7b89c2c..975b92e632b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -16,11 +16,12 @@ package com.android.systemui.statusbar.core import android.app.Fragment -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.fragments.FragmentHostManager +import com.android.systemui.res.R +import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener +import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions -import com.android.systemui.statusbar.phone.PhoneStatusBarView import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent @@ -33,50 +34,16 @@ import javax.inject.Provider * Responsible for creating the status bar window and initializing the root components of that * window (see [CollapsedStatusBarFragment]) */ -@SysUISingleton -class StatusBarInitializer @Inject constructor( - private val windowController: StatusBarWindowController, - private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>, - private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>, -) { +interface StatusBarInitializer { - var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null + var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? /** * Creates the status bar window and root views, and initializes the component. * * TODO(b/277764509): Initialize the status bar via [CoreStartable#start]. */ - fun initializeStatusBar() { - windowController.fragmentHostManager.addTagListener( - CollapsedStatusBarFragment.TAG, - object : FragmentHostManager.FragmentListener { - override fun onFragmentViewCreated(tag: String, fragment: Fragment) { - val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment) - .statusBarFragmentComponent ?: throw IllegalStateException() - statusBarViewUpdatedListener?.onStatusBarViewUpdated( - statusBarFragmentComponent.phoneStatusBarView, - statusBarFragmentComponent.phoneStatusBarViewController, - statusBarFragmentComponent.phoneStatusBarTransitions - ) - creationListeners.forEach { listener -> - listener.onStatusBarViewInitialized(statusBarFragmentComponent) - } - } - - override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) { - // nop - } - } - ).fragmentManager - .beginTransaction() - .replace( - R.id.status_bar_container, - collapsedStatusBarFragmentProvider.get(), - CollapsedStatusBarFragment.TAG - ) - .commit() - } + fun initializeStatusBar() interface OnStatusBarViewInitializedListener { @@ -84,16 +51,61 @@ class StatusBarInitializer @Inject constructor( * The status bar view has been initialized. * * @param component Dagger component that is created when the status bar view is created. - * Can be used to retrieve dependencies from that scope, including the status bar root view. + * Can be used to retrieve dependencies from that scope, including the status bar root + * view. */ fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) } interface OnStatusBarViewUpdatedListener { fun onStatusBarViewUpdated( - statusBarView: PhoneStatusBarView, statusBarViewController: PhoneStatusBarViewController, - statusBarTransitions: PhoneStatusBarTransitions + statusBarTransitions: PhoneStatusBarTransitions, ) } } + +@SysUISingleton +class StatusBarInitializerImpl +@Inject +constructor( + private val windowController: StatusBarWindowController, + private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>, + private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>, +) : StatusBarInitializer { + + override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null + + override fun initializeStatusBar() { + windowController.fragmentHostManager + .addTagListener( + CollapsedStatusBarFragment.TAG, + object : FragmentHostManager.FragmentListener { + override fun onFragmentViewCreated(tag: String, fragment: Fragment) { + val statusBarFragmentComponent = + (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent + ?: throw IllegalStateException() + statusBarViewUpdatedListener?.onStatusBarViewUpdated( + statusBarFragmentComponent.phoneStatusBarViewController, + statusBarFragmentComponent.phoneStatusBarTransitions, + ) + creationListeners.forEach { listener -> + listener.onStatusBarViewInitialized(statusBarFragmentComponent) + } + } + + override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) { + // nop + } + }, + ) + .fragmentManager + .beginTransaction() + .replace( + R.id.status_bar_container, + collapsedStatusBarFragmentProvider.get(), + CollapsedStatusBarFragment.TAG, + ) + .commit() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt index 601fbfaf1b64..214151383dc6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.android.systemui.keyguard.shared +package com.android.systemui.statusbar.core import com.android.systemui.Flags import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils -/** Helper for reading or using the compose lockscreen flag state. */ -@Suppress("NOTHING_TO_INLINE") -object ComposeLockscreen { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN +/** Helper for reading and using the status bar simple fragment flag state */ +object StatusBarSimpleFragment { + /** Aconfig flag for removing the fragment */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +32,7 @@ object ComposeLockscreen { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.composeLockscreen() + get() = Flags.statusBarSimpleFragment() /** * Called to ensure code is only run when the flag is enabled. This protects users from the @@ -46,6 +45,14 @@ object ComposeLockscreen { /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + * Caution!! Using this check incorrectly will cause crashes in nextfood builds! + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 406a66449f82..526c64c15696 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -20,12 +20,16 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory +import com.android.systemui.statusbar.core.StatusBarInitializer +import com.android.systemui.statusbar.core.StatusBarInitializerImpl import com.android.systemui.statusbar.data.StatusBarDataLayerModule import com.android.systemui.statusbar.phone.LightBarController import com.android.systemui.statusbar.phone.StatusBarSignalPolicy import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl +import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -57,6 +61,13 @@ abstract class StatusBarModule { @ClassKey(StatusBarSignalPolicy::class) abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable + @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer + + @Binds + abstract fun statusBarWindowController( + impl: StatusBarWindowControllerImpl + ): StatusBarWindowController + companion object { @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt index c0302bc348b6..9af4b8c18c86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt @@ -25,6 +25,7 @@ import dagger.Module import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow /** * Repository used for tracking the state of notification remote input (e.g. when the user presses @@ -33,14 +34,21 @@ import kotlinx.coroutines.flow.Flow interface RemoteInputRepository { /** Whether remote input is currently active for any notification. */ val isRemoteInputActive: Flow<Boolean> + + /** + * The bottom bound of the currently focused remote input notification row, or null if there + * isn't one. + */ + val remoteInputRowBottomBound: Flow<Float?> + + fun setRemoteInputRowBottomBound(bottom: Float?) } @SysUISingleton class RemoteInputRepositoryImpl @Inject -constructor( - private val notificationRemoteInputManager: NotificationRemoteInputManager, -) : RemoteInputRepository { +constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) : + RemoteInputRepository { override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow { trySend(false) // initial value is false val callback = @@ -52,6 +60,12 @@ constructor( notificationRemoteInputManager.addControllerCallback(callback) awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) } } + + override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null) + + override fun setRemoteInputRowBottomBound(bottom: Float?) { + remoteInputRowBottomBound.value = bottom + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt index 68f727b046c0..b83b0cc8d2c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt @@ -20,13 +20,24 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.data.repository.RemoteInputRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull /** * Interactor used for business logic pertaining to the notification remote input (e.g. when the * user presses "reply" on a notification and the keyboard opens). */ @SysUISingleton -class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) { +class RemoteInputInteractor +@Inject +constructor(private val remoteInputRepository: RemoteInputRepository) { /** Is remote input currently active for a notification? */ val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive + + /** The bottom bound of the currently focused remote input notification row. */ + val remoteInputRowBottomBound: Flow<Float> = + remoteInputRepository.remoteInputRowBottomBound.mapNotNull { it } + + fun setRemoteInputRowBottomBound(bottom: Float?) { + remoteInputRepository.setRemoteInputRowBottomBound(bottom) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index cb3e26b9f8ea..5003a6af5c4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -21,6 +21,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; +import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE; import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.animation.Animator; @@ -83,6 +84,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarIconView; @@ -118,6 +120,7 @@ import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; +import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; @@ -830,6 +833,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.setRemoteInputController(r); } + /** + * Return the cumulative y-value that the actions container expands via its scale animator when + * remote input is activated. + */ + public float getRemoteInputActionsContainerExpandedOffset() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + RemoteInputView expandedRemoteInput = mPrivateLayout.getExpandedRemoteInput(); + if (expandedRemoteInput == null) return 0f; + View actionsContainerLayout = expandedRemoteInput.getActionsContainerLayout(); + if (actionsContainerLayout == null) return 0f; + + return actionsContainerLayout.getHeight() * (1 - FOCUS_ANIMATION_MIN_SCALE) * 0.5f; + } + public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 7543f3b48e48..e7c67f93eb78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -99,6 +99,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.FakeShadowView; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -120,7 +121,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape; import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; @@ -740,6 +740,15 @@ public class NotificationStackScrollLayout updateFooter(); } + void sendRemoteInputRowBottomBound(Float bottom) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + if (bottom != null) { + bottom += getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin); + } + mScrollViewFields.sendRemoteInputRowBottomBound(bottom); + } + /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */ public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { FooterViewRefactor.assertInLegacyMode(); @@ -1274,6 +1283,11 @@ public class NotificationStackScrollLayout } @Override + public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer<Float> consumer) { + mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer); + } + + @Override public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setHeadsUpHeightConsumer(consumer); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e5f63c1cb480..dad6894a43ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -98,6 +98,9 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; @@ -129,9 +132,6 @@ import com.android.systemui.statusbar.notification.row.NotificationSnooze; import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -1605,6 +1605,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { return new RemoteInputController.Delegate() { public void setRemoteInputActive(NotificationEntry entry, boolean remoteInputActive) { + if (SceneContainerFlag.isEnabled()) { + sendRemoteInputRowBottomBound(entry, remoteInputActive); + } mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); if (!FooterViewRefactor.isEnabled()) { @@ -1620,6 +1623,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.requestDisallowLongPress(); mView.requestDisallowDismiss(); } + + private void sendRemoteInputRowBottomBound(NotificationEntry entry, + boolean remoteInputActive) { + ExpandableNotificationRow row = entry.getRow(); + float top = row.getTranslationY(); + int height = row.getActualHeight(); + float bottom = top + height + row.getRemoteInputActionsContainerExpandedOffset(); + mView.sendRemoteInputRowBottomBound(remoteInputActive ? bottom : null); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index aa3953987c10..c08ed6120832 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -57,6 +57,13 @@ class ScrollViewFields { * guts off of this gesture, we can notify the placeholder through here. */ var currentGestureInGutsConsumer: Consumer<Boolean>? = null + + /** + * When a notification begins remote input, its bottom Y bound is sent to the placeholder + * through here in order to adjust to accommodate the IME. + */ + var remoteInputRowBottomBoundConsumer: Consumer<Float?>? = null + /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder @@ -75,6 +82,10 @@ class ScrollViewFields { fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) = currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts) + /** send [bottomY] to the [remoteInputRowBottomBoundConsumer], if present. */ + fun sendRemoteInputRowBottomBound(bottomY: Float?) = + remoteInputRowBottomBoundConsumer?.accept(bottomY) + /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 235b4da3f029..41c02934efa6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -74,6 +74,9 @@ interface NotificationScrollView { /** Set a consumer for current gesture in guts events */ fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for current remote input notification row bottom bound events */ + fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer<Float?>?) + /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 6d5553fec6b4..2e37dead8787 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -108,10 +108,14 @@ constructor( view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer) + view.setRemoteInputRowBottomBoundConsumer( + viewModel.remoteInputRowBottomBoundConsumer + ) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) view.setCurrentGestureInGutsConsumer(null) + view.setRemoteInputRowBottomBoundConsumer(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 8d7007b2fba4..5b2e02d446cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape @@ -56,6 +57,7 @@ constructor( dumpManager: DumpManager, stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, + private val remoteInputInteractor: RemoteInputInteractor, private val sceneInteractor: SceneInteractor, // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released - // while the flag is off, creating this object too early results in a crash @@ -240,6 +242,10 @@ constructor( val currentGestureInGutsConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureInGuts + /** Receives the bottom bound of the currently focused remote input notification row. */ + val remoteInputRowBottomBoundConsumer: (Float?) -> Unit = + remoteInputInteractor::setRemoteInputRowBottomBound + /** Whether the notification stack is scrollable or not. */ val isScrollable: Flow<Boolean> = combine(sceneInteractor.currentScene, sceneInteractor.currentOverlays) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 69c1bf3b61b7..c8e83581e831 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds @@ -49,6 +50,7 @@ constructor( private val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, private val headsUpNotificationInteractor: HeadsUpNotificationInteractor, + remoteInputInteractor: RemoteInputInteractor, featureFlags: FeatureFlagsClassic, dumpManager: DumpManager, ) : @@ -132,6 +134,12 @@ constructor( val isCurrentGestureOverscroll: Flow<Boolean> = interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll") + /** Whether remote input is currently active for any notification. */ + val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive + + /** The bottom bound of the currently focused remote input notification row. */ + val remoteInputRowBottomBound = remoteInputInteractor.remoteInputRowBottomBound + /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { interactor.setScrolledToTop(scrolledToTop) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 50e92498b114..59533b343a57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -372,7 +372,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Point mCurrentDisplaySize = new Point(); - protected PhoneStatusBarView mStatusBarView; private PhoneStatusBarViewController mPhoneStatusBarViewController; private PhoneStatusBarTransitions mStatusBarTransitions; private final AuthRippleController mAuthRippleController; @@ -1191,8 +1190,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up CollapsedStatusBarFragment and PhoneStatusBarView mStatusBarInitializer.setStatusBarViewUpdatedListener( - (statusBarView, statusBarViewController, statusBarTransitions) -> { - mStatusBarView = statusBarView; + (statusBarViewController, statusBarTransitions) -> { mPhoneStatusBarViewController = statusBarViewController; mStatusBarTransitions = statusBarTransitions; getNotificationShadeWindowViewController() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index c0e36b2ab42a..f026b99af49c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -27,7 +27,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarLocation; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; @@ -114,14 +113,6 @@ public interface StatusBarFragmentModule { /** */ @Provides @StatusBarFragmentScope - static StatusBarUserSwitcherContainer provideStatusBarUserSwitcherContainer( - @RootView PhoneStatusBarView view) { - return view.findViewById(R.id.user_switcher_container); - } - - /** */ - @Provides - @StatusBarFragmentScope static PhoneStatusBarViewController providePhoneStatusBarViewController( PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, @RootView PhoneStatusBarView phoneStatusBarView) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 31776cf5ad1b..16d5f8d30593 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -106,7 +106,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50; private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33; private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83; - private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f; + public static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f; private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120; private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt index dbeaa59cd219..ba45942177a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -27,7 +27,10 @@ import com.android.settingslib.notification.modes.ZenIcon import com.android.settingslib.notification.modes.ZenIconLoader import com.android.settingslib.notification.modes.ZenMode import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.modes.shared.ModesUi import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository +import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository +import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo import java.time.Duration @@ -51,7 +54,17 @@ constructor( private val notificationSettingsRepository: NotificationSettingsRepository, @Background private val bgDispatcher: CoroutineDispatcher, private val iconLoader: ZenIconLoader, + private val deviceProvisioningRepository: DeviceProvisioningRepository, + private val userSetupRepository: UserSetupRepository, ) { + val isZenAvailable: Flow<Boolean> = + combine( + deviceProvisioningRepository.isDeviceProvisioned, + userSetupRepository.isUserSetUp, + ) { isDeviceProvisioned, isUserSetUp -> + isDeviceProvisioned && isUserSetUp + } + val isZenModeEnabled: Flow<Boolean> = zenModeRepository.globalZenMode .map { @@ -80,6 +93,18 @@ constructor( val modes: Flow<List<ZenMode>> = zenModeRepository.modes + /** + * Returns the special "manual DND" mode. + * + * This is only meant as a temporary solution for "legacy" UI pieces that handle DND + * specifically; any new or migrated features should use modes more generally, through [modes] + * or [activeModes]. + */ + val dndMode: Flow<ZenMode?> by lazy { + ModesUi.assertInNewMode() + zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } } + } + /** Flow returning the currently active mode(s), if any. */ val activeModes: Flow<ActiveZenModes> = modes @@ -113,10 +138,11 @@ constructor( Log.e( TAG, "Interactor cannot handle showing the zen duration prompt. " + - "Please use EnableZenModeDialog when this setting is active." + "Please use EnableZenModeDialog when this setting is active.", ) null } + ZEN_DURATION_FOREVER -> null else -> Duration.ofMinutes(zenDuration.toLong()) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt index af93880bad51..27bc6d36c1e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt @@ -59,32 +59,26 @@ fun ModeTile(viewModel: ModeTileViewModel) { ) CompositionLocalProvider(LocalContentColor provides contentColor) { - Surface( - color = tileColor, - shape = RoundedCornerShape(16.dp), - ) { + Surface(color = tileColor, shape = RoundedCornerShape(16.dp)) { Row( modifier = Modifier.combinedClickable( onClick = viewModel.onClick, onLongClick = viewModel.onLongClick, - onLongClickLabel = viewModel.onLongClickLabel + onLongClickLabel = viewModel.onLongClickLabel, ) - .padding(20.dp) + .padding(16.dp) .semantics { stateDescription = viewModel.stateDescription }, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = - Arrangement.spacedBy( - space = 10.dp, - alignment = Alignment.Start, - ), + Arrangement.spacedBy(space = 8.dp, alignment = Alignment.Start), ) { Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp)) Column { Text( viewModel.text, fontWeight = FontWeight.W500, - modifier = Modifier.tileMarquee().testTag("name") + modifier = Modifier.tileMarquee().testTag("name"), ) Text( viewModel.subtext, @@ -94,7 +88,7 @@ fun ModeTile(viewModel: ModeTileViewModel) { .testTag(if (viewModel.enabled) "stateOn" else "stateOff") .clearAndSetSemantics { contentDescription = viewModel.subtextDescription - } + }, ) } } @@ -103,8 +97,5 @@ fun ModeTile(viewModel: ModeTileViewModel) { } private fun Modifier.tileMarquee(): Modifier { - return this.basicMarquee( - iterations = 1, - initialDelayMillis = 200, - ) + return this.basicMarquee(iterations = 1) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt index 73d361f69eac..5953ea598929 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy.ui.dialog.composable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.runtime.Composable @@ -27,23 +26,20 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.Flags import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel @Composable fun ModeTileGrid(viewModel: ModesDialogViewModel) { val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList()) - // TODO(b/346519570): Handle what happens when we have more than a few modes. LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.padding(8.dp).fillMaxWidth().heightIn(max = 300.dp), + columns = GridCells.Fixed(if (Flags.modesDialogSingleRows()) 1 else 2), + modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - items( - tiles.size, - key = { index -> tiles[index].id }, - ) { index -> + items(tiles.size, key = { index -> tiles[index].id }) { index -> ModeTile(viewModel = tiles[index]) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt new file mode 100644 index 000000000000..421e5c45bbfe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt @@ -0,0 +1,76 @@ +/* + * 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.statusbar.window + +import android.view.View +import android.view.ViewGroup +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.fragments.FragmentHostManager +import java.util.Optional + +/** Encapsulates all logic for the status bar window state management. */ +interface StatusBarWindowController { + val statusBarHeight: Int + + /** Rereads the status bar height and reapplies the current state if the height is different. */ + fun refreshStatusBarHeight() + + /** Adds the status bar view to the window manager. */ + fun attach() + + /** Adds the given view to the status bar window view. */ + fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) + + /** Returns the status bar window's background view. */ + val backgroundView: View + + /** Returns a fragment host manager for the status bar window view. */ + val fragmentHostManager: FragmentHostManager + + /** + * Provides an updated animation controller if we're animating a view in the status bar. + * + * This is needed because we have to make sure that the status bar window matches the full + * screen during the animation and that we are expanding the view below the other status bar + * text. + * + * @param rootView the root view of the animation + * @param animationController the default animation controller to use + * @return If the animation is on a view in the status bar, returns an Optional containing an + * updated animation controller that handles status-bar-related animation details. Returns an + * empty optional if the animation is *not* on a view in the status bar. + */ + fun wrapAnimationControllerIfInStatusBar( + rootView: View, + animationController: ActivityTransitionAnimator.Controller, + ): Optional<ActivityTransitionAnimator.Controller> + + /** Set force status bar visible. */ + fun setForceStatusBarVisible(forceStatusBarVisible: Boolean) + + /** + * Sets whether an ongoing process requires the status bar to be forced visible. + * + * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing + * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to + * false but this method is set to true, then the status bar **will** be visible. + * + * TODO(b/195839150): We should likely merge this method and {@link + * this#setForceStatusBarVisible} together and use some sort of ranking system instead. + */ + fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java index c30a6b7d0f17..1a0327cdd809 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java @@ -46,6 +46,8 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; +import androidx.annotation.NonNull; + import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ActivityTransitionAnimator; @@ -67,7 +69,7 @@ import javax.inject.Inject; * Encapsulates all logic for the status bar window state management. */ @SysUISingleton -public class StatusBarWindowController { +public class StatusBarWindowControllerImpl implements StatusBarWindowController { private static final String TAG = "StatusBarWindowController"; private static final boolean DEBUG = false; @@ -89,7 +91,7 @@ public class StatusBarWindowController { private final Binder mInsetsSourceOwner = new Binder(); @Inject - public StatusBarWindowController( + public StatusBarWindowControllerImpl( Context context, @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, @@ -117,14 +119,12 @@ public class StatusBarWindowController { /* attachedViewProvider=*/ () -> mStatusBarWindowView))); } + @Override public int getStatusBarHeight() { return mBarHeight; } - /** - * Rereads the status bar height and reapplys the current state if the height - * is different. - */ + @Override public void refreshStatusBarHeight() { Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight"); try { @@ -141,9 +141,7 @@ public class StatusBarWindowController { } } - /** - * Adds the status bar view to the window manager. - */ + @Override public void attach() { // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is @@ -161,54 +159,47 @@ public class StatusBarWindowController { apply(mCurrentState); } - /** Adds the given view to the status bar window view. */ - public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) { + @Override + public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) { mStatusBarWindowView.addView(view, layoutParams); } - /** Returns the status bar window's background view. */ + @NonNull + @Override public View getBackgroundView() { return mStatusBarWindowView.findViewById(R.id.status_bar_container); } - /** Returns a fragment host manager for the status bar window view. */ + @NonNull + @Override public FragmentHostManager getFragmentHostManager() { return mFragmentService.getFragmentHostManager(mStatusBarWindowView); } - /** - * Provides an updated animation controller if we're animating a view in the status bar. - * - * This is needed because we have to make sure that the status bar window matches the full - * screen during the animation and that we are expanding the view below the other status bar - * text. - * - * @param rootView the root view of the animation - * @param animationController the default animation controller to use - * @return If the animation is on a view in the status bar, returns an Optional containing an - * updated animation controller that handles status-bar-related animation details. Returns an - * empty optional if the animation is *not* on a view in the status bar. - */ + @NonNull + @Override public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar( - View rootView, ActivityTransitionAnimator.Controller animationController) { + @NonNull View rootView, + @NonNull ActivityTransitionAnimator.Controller animationController) { if (rootView != mStatusBarWindowView) { return Optional.empty(); } animationController.setTransitionContainer(mLaunchAnimationContainer); - return Optional.of(new DelegateTransitionAnimatorController(animationController) { - @Override - public void onTransitionAnimationStart(boolean isExpandingFullyAbove) { - getDelegate().onTransitionAnimationStart(isExpandingFullyAbove); - setLaunchAnimationRunning(true); - } - - @Override - public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) { - getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove); - setLaunchAnimationRunning(false); - } - }); + return Optional.of( + new DelegateTransitionAnimatorController(animationController) { + @Override + public void onTransitionAnimationStart(boolean isExpandingFullyAbove) { + getDelegate().onTransitionAnimationStart(isExpandingFullyAbove); + setLaunchAnimationRunning(true); + } + + @Override + public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) { + getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove); + setLaunchAnimationRunning(false); + } + }); } private WindowManager.LayoutParams getBarLayoutParams(int rotation) { @@ -275,22 +266,13 @@ public class StatusBarWindowController { } } - /** Set force status bar visible. */ + @Override public void setForceStatusBarVisible(boolean forceStatusBarVisible) { mCurrentState.mForceStatusBarVisible = forceStatusBarVisible; apply(mCurrentState); } - /** - * Sets whether an ongoing process requires the status bar to be forced visible. - * - * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing - * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to - * false but this method is set to true, then the status bar **will** be visible. - * - * TODO(b/195839150): We should likely merge this method and - * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead. - */ + @Override public void setOngoingProcessRequiresStatusBarVisible(boolean visible) { mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible; apply(mCurrentState); diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt index 1a41987a8e5b..80ea925eabc7 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt @@ -30,12 +30,12 @@ class TouchpadGesturesInteractor( private val logger: InputDeviceTutorialLogger, ) { fun disableGestures() { - logger.log("Disabling touchpad gestures across the system") + logger.d("Disabling touchpad gestures across the system") setGesturesState(disabled = true) } fun enableGestures() { - logger.log("Enabling touchpad gestures across the system") + logger.d("Enabling touchpad gestures across the system") setGesturesState(disabled = false) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index 6fe65478b6e1..d03b2e717398 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -57,6 +57,7 @@ constructor( } // required to handle 3+ fingers on touchpad window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) + logger.logOpenTutorial(TutorialContext.TOUCHPAD_TUTORIAL) } private fun finishTutorial() { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index db4f9ef13bd6..7166428d863f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -35,7 +35,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix; -import static com.android.systemui.Flags.hapticVolumeSlider; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -928,10 +927,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } private void addSliderHapticsToRow(VolumeRow row) { - if (hapticVolumeSlider()) { - row.createPlugin(mVibratorHelper, mSystemClock); - HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); - } + row.createPlugin(mVibratorHelper, mSystemClock); + HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); } @VisibleForTesting void addSliderHapticsToRows() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 103449b6b0f7..ee8ce17cecd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.UiModeManager; +import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; import android.platform.test.annotations.EnableFlags; @@ -78,6 +79,12 @@ public class MenuViewTest extends SysuiTestCase { mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); + // Programmatically update the resource's configuration to night mode to reduce flakiness + Configuration nightConfig = new Configuration(mContext.getResources().getConfiguration()); + nightConfig.uiMode = Configuration.UI_MODE_NIGHT_YES; + mContext.getResources().updateConfiguration(nightConfig, + mContext.getResources().getDisplayMetrics(), null); + mSpyContext = spy(mContext); doNothing().when(mSpyContext).startActivity(any()); @@ -101,6 +108,8 @@ public class MenuViewTest extends SysuiTestCase { @Test public void insetsOnDarkTheme_menuOnLeft_matchInsets() { + // In dark theme, the inset is not 0 to avoid weird spacing issue between the menu and + // the edge of the screen. mMenuView.onConfigurationChanged(/* newConfig= */ null); final InstantInsetLayerDrawable insetLayerDrawable = (InstantInsetLayerDrawable) mMenuView.getBackground(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt index 4b61a0d02f1e..088bb02512b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory +import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.lifecycle.activateIn import com.android.systemui.motion.createSysUiComposeMotionTestRule import com.android.systemui.testKosmos @@ -55,6 +56,7 @@ class PatternBouncerTest : SysuiTestCase() { kosmos.patternBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true).asStateFlow(), onIntentionalUserInput = {}, + bouncerHapticPlayer = kosmos.bouncerHapticPlayer, ) @Before @@ -75,11 +77,11 @@ class PatternBouncerTest : SysuiTestCase() { content = { play -> if (play) PatternBouncerUnderTest() }, ComposeRecordingSpec.until( recordBefore = false, - checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) } + checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) }, ) { feature(MotionTestKeys.dotAppearFadeIn, floatArray) feature(MotionTestKeys.dotAppearMoveUp, floatArray) - } + }, ) assertThat(motion).timeSeriesMatchesGolden() @@ -100,7 +102,7 @@ class PatternBouncerTest : SysuiTestCase() { viewModel.onDragEnd() // Failure animation starts when animateFailure flips to true... viewModel.animateFailure.takeWhile { !it }.collect {} - } + }, ) { // ... and ends when the composable flips it back to false. viewModel.animateFailure.takeWhile { it }.collect {} @@ -111,7 +113,7 @@ class PatternBouncerTest : SysuiTestCase() { content = { PatternBouncerUnderTest() }, ComposeRecordingSpec(failureAnimationMotionControl) { feature(MotionTestKeys.dotScaling, floatArray) - } + }, ) assertThat(motion).timeSeriesMatchesGolden() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java index c65a1176a55b..d72b72c3d21e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -32,6 +32,7 @@ import android.content.ClipData; import android.content.ClipDescription; import android.content.ClipboardManager; import android.os.PersistableBundle; +import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; @@ -101,8 +102,18 @@ public class ClipboardListenerTest extends SysuiTestCase { when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData); when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource); - mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider, - mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger); + mClipboardListener = new ClipboardListener( + getContext(), + mOverlayControllerProvider, + mClipboardToast, + user -> { + if (UserHandle.CURRENT.equals(user)) { + return mClipboardManager; + } + return null; + }, + mKeyguardManager, + mUiEventLogger); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 411ff91ebc2f..8731853e4939 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -77,7 +77,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private static final int TEST_CURRENT_VOLUME = 10; // Mock - private MediaOutputController mMediaOutputController = mock(MediaOutputController.class); + private MediaSwitchingController mMediaSwitchingController = + mock(MediaSwitchingController.class); private MediaOutputDialog mMediaOutputDialog = mock(MediaOutputDialog.class); private MediaDevice mMediaDevice1 = mock(MediaDevice.class); private MediaDevice mMediaDevice2 = mock(MediaDevice.class); @@ -95,13 +96,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Before public void setUp() { - when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems); - when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false); - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); - when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat); - when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat); - when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1); - when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true); + when(mMediaSwitchingController.getMediaItemList()).thenReturn(mMediaItems); + when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(false); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat); + when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat); + when(mMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1); + when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true); when(mIconCompat.toIcon(mContext)).thenReturn(mIcon); when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1); when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1); @@ -116,7 +117,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1)); mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2)); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -142,7 +143,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindPairNew_verifyView() { - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -161,11 +162,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroup_withSessionName_verifyView() { - when(mMediaOutputController.getSelectedMediaDevice()).thenReturn( - mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect( - Collectors.toList())); - when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + when(mMediaSwitchingController.getSelectedMediaDevice()) + .thenReturn( + mMediaItems.stream() + .map((item) -> item.getMediaDevice().get()) + .collect(Collectors.toList())); + when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -181,11 +184,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroup_noSessionName_verifyView() { - when(mMediaOutputController.getSelectedMediaDevice()).thenReturn( - mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect( - Collectors.toList())); - when(mMediaOutputController.getSessionName()).thenReturn(null); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + when(mMediaSwitchingController.getSelectedMediaDevice()) + .thenReturn( + mMediaItems.stream() + .map((item) -> item.getMediaDevice().get()) + .collect(Collectors.toList())); + when(mMediaSwitchingController.getSessionName()).thenReturn(null); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -214,7 +219,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() { - when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); + when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -230,9 +235,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedRemoteDevice_verifyView() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice2)); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice2)); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -249,9 +254,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedRemoteDevice_verifyContentDescriptionNotNull() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice2)); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice2)); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -263,9 +268,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of()); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of()); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -283,9 +287,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedRemoteDeviceWithOnGoingSession_verifyView() { when(mMediaDevice1.hasOngoingSession()).thenReturn(true); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of()); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of()); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -305,9 +308,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { public void onBindViewHolder_bindConnectedRemoteDeviceWithHostOnGoingSession_verifyView() { when(mMediaDevice1.hasOngoingSession()).thenReturn(true); when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of()); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of()); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -326,8 +328,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() { - when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); @@ -340,8 +342,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_isMutingExpectedDevice_verifyView() { when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); - when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -378,14 +380,14 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar); assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); - verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1); + verify(mMediaSwitchingController).logInteractionAdjustVolume(mMediaDevice1); } @Test public void onBindViewHolder_bindSelectableDevice_verifyView() { List<MediaDevice> selectableDevices = new ArrayList<>(); selectableDevices.add(mMediaDevice2); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); @@ -440,7 +442,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void subStatusSupported_onBindViewHolder_bindHostDeviceWithOngoingSession_verifyView() { - when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); + when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true); when(mMediaDevice1.hasSubtext()).thenReturn(true); when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM); @@ -540,7 +542,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -556,7 +558,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroupingDevice_verifyView() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false); when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_GROUPING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -572,7 +574,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice2.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -586,7 +588,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() { - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -595,16 +597,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout); + verify(mMediaSwitchingController).launchBluetoothPairing(mViewHolder.mContainerLayout); } @Test public void onItemClick_clickDevice_verifyConnectDevice() { - when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false); + when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false); assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -613,16 +615,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).connectDevice(mMediaDevice2); + verify(mMediaSwitchingController).connectDevice(mMediaDevice2); } @Test public void onItemClick_clickDeviceWithSessionOngoing_verifyShowsDialog() { - when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true); + when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true); assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -633,66 +635,68 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1); spyMediaDeviceViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController, never()).connectDevice(mMediaDevice2); + verify(mMediaSwitchingController, never()).connectDevice(mMediaDevice2); verify(spyMediaDeviceViewHolder).showCustomEndSessionDialog(mMediaDevice2); } @Test public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); - when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false); when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(false); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).cancelMuteAwaitConnection(); + verify(mMediaSwitchingController).cancelMuteAwaitConnection(); } @Test public void onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() { List<MediaDevice> selectableDevices = new ArrayList<>(); selectableDevices.add(mMediaDevice2); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); mViewHolder.mEndTouchArea.performClick(); - verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2); + verify(mMediaSwitchingController).addDeviceToPlayMedia(mMediaDevice2); } @Test public void onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice2)); - when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice1)); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice2)); + when(mMediaSwitchingController.getDeselectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice1)); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); mViewHolder.mEndTouchArea.performClick(); - verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1); + verify(mMediaSwitchingController).removeDeviceFromPlayMedia(mMediaDevice1); } @Test public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() { - when(mMediaOutputController.getSelectedMediaDevice()).thenReturn( - mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect( - Collectors.toList())); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + when(mMediaSwitchingController.getSelectedMediaDevice()) + .thenReturn( + mMediaItems.stream() + .map((item) -> item.getMediaDevice().get()) + .collect(Collectors.toList())); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); List<MediaDevice> selectableDevices = new ArrayList<>(); selectableDevices.add(mMediaDevice1); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); - when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices); + when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(true); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); mViewHolder.mContainerLayout.performClick(); @@ -702,11 +706,11 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() { - when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false); + when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse(); - when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); + when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue(); @@ -719,7 +723,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.updateColorScheme(wallpaperColors, true); - verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true); + verify(mMediaSwitchingController).setCurrentColorScheme(wallpaperColors, true); } @Test @@ -727,7 +731,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.updateItems(); List<MediaItem> updatedList = new ArrayList<>(); updatedList.add(MediaItem.createPairNewDeviceMediaItem()); - when(mMediaOutputController.getMediaItemList()).thenReturn(updatedList); + when(mMediaSwitchingController.getMediaItemList()).thenReturn(updatedList); assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size()); mMediaOutputAdapter.updateItems(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index c8cc6b5fdf93..47371dfd8895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -104,7 +104,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private List<MediaController> mMediaControllers = new ArrayList<>(); private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; private int mHeaderIconRes; private IconCompat mIconCompat; private CharSequence mHeaderTitle; @@ -132,8 +132,8 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( mKosmos); - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mContext, TEST_PACKAGE, mContext.getUser(), @@ -153,12 +153,13 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { // Using a fake package will cause routing operations to fail, so we intercept // scanning-related operations. - mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class); - doNothing().when(mMediaOutputController.mLocalMediaManager).startScan(); - doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan(); + mMediaSwitchingController.mLocalMediaManager = mock(LocalMediaManager.class); + doNothing().when(mMediaSwitchingController.mLocalMediaManager).startScan(); + doNothing().when(mMediaSwitchingController.mLocalMediaManager).stopScan(); - mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, - mMediaOutputController); + mMediaOutputBaseDialogImpl = + new MediaOutputBaseDialogImpl( + mContext, mBroadcastSender, mMediaSwitchingController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); } @@ -176,7 +177,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { public void refresh_withIconCompat_iconIsVisible() { mIconCompat = IconCompat.createWithBitmap( Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)); - when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaOutputController); + when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaSwitchingController); mMediaOutputBaseDialogImpl.refresh(); final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById( @@ -263,7 +264,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { when(mMediaOutputBaseAdapter.isDragging()).thenReturn(true); mMediaOutputBaseDialogImpl.refresh(); - assertThat(mMediaOutputController.isRefreshing()).isFalse(); + assertThat(mMediaSwitchingController.isRefreshing()).isFalse(); } @Test @@ -335,12 +336,14 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog { - MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController) { + MediaOutputBaseDialogImpl( + Context context, + BroadcastSender broadcastSender, + MediaSwitchingController mediaSwitchingController) { super( context, broadcastSender, - mediaOutputController, /* includePlaybackAndAppMetadata */ + mediaSwitchingController, /* includePlaybackAndAppMetadata */ true); mAdapter = mMediaOutputBaseAdapter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 189a56145d27..f0902e35b837 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -119,7 +119,7 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { private UserTracker mUserTracker = mock(UserTracker.class); private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; @Before public void setUp() { @@ -133,8 +133,8 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( mKosmos); - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mContext, TEST_PACKAGE, mContext.getUser(), @@ -151,9 +151,10 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { mFlags, volumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false, - mBroadcastSender, mMediaOutputController); + mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; + mMediaOutputBroadcastDialog = + new MediaOutputBroadcastDialog( + mContext, false, mBroadcastSender, mMediaSwitchingController); mMediaOutputBroadcastDialog.show(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 90c2930f8e49..d3ecb3d8c944 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -119,7 +119,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private List<MediaController> mMediaControllers = new ArrayList<>(); private MediaOutputDialog mMediaOutputDialog; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; private final List<String> mFeatures = new ArrayList<>(); @Override @@ -146,8 +146,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( mKosmos); - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mContext, TEST_PACKAGE, mContext.getUser(), @@ -164,8 +164,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { mFlags, volumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputDialog = makeTestDialog(mMediaOutputController); + mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; + mMediaOutputDialog = makeTestDialog(mMediaSwitchingController); mMediaOutputDialog.show(); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); @@ -388,12 +388,15 @@ public class MediaOutputDialogTest extends SysuiTestCase { public void getStopButtonText_notSupportsBroadcast_returnsDefaultText() { String stopText = mContext.getText( R.string.media_output_dialog_button_stop_casting).toString(); - MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); - when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); - - withTestDialog(mockMediaOutputController, testDialog -> { - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); - }); + MediaSwitchingController mockMediaSwitchingController = + mock(MediaSwitchingController.class); + when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false); + + withTestDialog( + mockMediaSwitchingController, + testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -401,28 +404,35 @@ public class MediaOutputDialogTest extends SysuiTestCase { public void getStopButtonText_supportsBroadcast_returnsBroadcastText() { String stopText = mContext.getText(R.string.media_output_broadcast).toString(); MediaDevice mMediaDevice = mock(MediaDevice.class); - MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); - when(mockMediaOutputController.isBroadcastSupported()).thenReturn(true); - when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice); - when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true); - when(mockMediaOutputController.isPlaying()).thenReturn(true); - when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false); - withTestDialog(mockMediaOutputController, testDialog -> { - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); - }); + MediaSwitchingController mockMediaSwitchingController = + mock(MediaSwitchingController.class); + when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(true); + when(mockMediaSwitchingController.getCurrentConnectedMediaDevice()) + .thenReturn(mMediaDevice); + when(mockMediaSwitchingController.isBluetoothLeDevice(any())).thenReturn(true); + when(mockMediaSwitchingController.isPlaying()).thenReturn(true); + when(mockMediaSwitchingController.isBluetoothLeBroadcastEnabled()).thenReturn(false); + withTestDialog( + mockMediaSwitchingController, + testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test public void onStopButtonClick_notPlaying_releaseSession() { - MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); - when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); - when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null); - when(mockMediaOutputController.isPlaying()).thenReturn(false); - withTestDialog(mockMediaOutputController, testDialog -> { - testDialog.onStopButtonClick(); - }); - - verify(mockMediaOutputController).releaseSession(); + MediaSwitchingController mockMediaSwitchingController = + mock(MediaSwitchingController.class); + when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false); + when(mockMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(null); + when(mockMediaSwitchingController.isPlaying()).thenReturn(false); + withTestDialog( + mockMediaSwitchingController, + testDialog -> { + testDialog.onStopButtonClick(); + }); + + verify(mockMediaSwitchingController).releaseSession(); verify(mDialogTransitionAnimator).disableAllCurrentDialogsExitAnimations(); } @@ -430,14 +440,14 @@ public class MediaOutputDialogTest extends SysuiTestCase { // Check the visibility metric logging by creating a new MediaOutput dialog, // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { - withTestDialog(mMediaOutputController, testDialog -> {}); + withTestDialog(mMediaSwitchingController, testDialog -> {}); verify(mUiEventLogger, times(2)) .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW); } @NonNull - private MediaOutputDialog makeTestDialog(MediaOutputController controller) { + private MediaOutputDialog makeTestDialog(MediaSwitchingController controller) { return new MediaOutputDialog( mContext, false, @@ -448,7 +458,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { true); } - private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) { + private void withTestDialog( + MediaSwitchingController controller, Consumer<MediaOutputDialog> c) { MediaOutputDialog testDialog = makeTestDialog(controller); testDialog.show(); c.accept(testDialog); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index 714fad9d7478..d3e20c6e39b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -43,6 +43,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.MediaDescription; import android.media.MediaMetadata; @@ -58,6 +59,7 @@ import android.os.Bundle; import android.os.PowerExemptionManager; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; import android.text.TextUtils; @@ -67,8 +69,10 @@ import androidx.core.graphics.drawable.IconCompat; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.media.flags.Flags; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.media.InputMediaDevice; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; @@ -101,7 +105,7 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class MediaOutputControllerTest extends SysuiTestCase { +public class MediaSwitchingControllerTest extends SysuiTestCase { private static final String TEST_DEVICE_1_ID = "test_device_1_id"; private static final String TEST_DEVICE_2_ID = "test_device_2_id"; private static final String TEST_DEVICE_3_ID = "test_device_3_id"; @@ -126,8 +130,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; - @Mock - private MediaOutputController.Callback mCb; + @Mock private MediaSwitchingController.Callback mCb; @Mock private MediaDevice mMediaDevice1; @Mock @@ -166,7 +169,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { private FeatureFlags mFlags = mock(FeatureFlags.class); private View mDialogLaunchView = mock(View.class); - private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class); + private MediaSwitchingController.Callback mCallback = + mock(MediaSwitchingController.Callback.class); final Notification mNotification = mock(Notification.class); private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor = @@ -175,7 +179,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { private Context mSpyContext; private String mPackageName = null; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; private LocalMediaManager mLocalMediaManager; private List<MediaController> mMediaControllers = new ArrayList<>(); private List<MediaDevice> mMediaDevices = new ArrayList<>(); @@ -203,9 +207,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn( mCachedBluetoothDeviceManager); - - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, mPackageName, mContext.getUser(), @@ -222,9 +225,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { mFlags, mVolumePanelGlobalStateInteractor, mUserTracker); - mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); + mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager); when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); - mMediaOutputController.mLocalMediaManager = mLocalMediaManager; + mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; MediaDescription.Builder builder = new MediaDescription.Builder(); builder.setTitle(TEST_SONG); builder.setSubtitle(TEST_ARTIST); @@ -264,26 +267,26 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void start_verifyLocalMediaManagerInit() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - verify(mLocalMediaManager).registerCallback(mMediaOutputController); + verify(mLocalMediaManager).registerCallback(mMediaSwitchingController); verify(mLocalMediaManager).startScan(); } @Test public void stop_verifyLocalMediaManagerDeinit() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mLocalMediaManager); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); - verify(mLocalMediaManager).unregisterCallback(mMediaOutputController); + verify(mLocalMediaManager).unregisterCallback(mMediaSwitchingController); verify(mLocalMediaManager).stopScan(); } @Test public void start_notificationNotFound_mediaControllerInitFromSession() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mSessionMediaController).registerCallback(any()); } @@ -291,7 +294,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void start_MediaNotificationFound_mediaControllerNotInitFromSession() { when(mNotification.isMediaNotification()).thenReturn(true); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mSessionMediaController, never()).registerCallback(any()); verifyZeroInteractions(mMediaSessionManager); @@ -299,8 +302,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void start_withoutPackageName_verifyMediaControllerInit() { - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mContext.getUser(), @@ -318,32 +321,32 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mSessionMediaController, never()).registerCallback(any()); } @Test public void start_nearbyMediaDevicesManagerNotNull_registersNearbyDevicesCallback() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mNearbyMediaDevicesManager).registerNearbyDevicesCallback(any()); } @Test public void stop_withPackageName_verifyMediaControllerDeinit() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mSessionMediaController); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); verify(mSessionMediaController).unregisterCallback(any()); } @Test public void stop_withoutPackageName_verifyMediaControllerDeinit() { - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -361,26 +364,26 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); verify(mSessionMediaController, never()).unregisterCallback(any()); } @Test public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mSessionMediaController); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any()); } @Test public void tryToLaunchMediaApplication_nullIntent_skip() { - mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView); + mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView); verify(mCb, never()).dismissDialog(); } @@ -391,9 +394,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { .thenReturn(mController); Intent intent = new Intent(mPackageName); doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(mPackageName); - mMediaOutputController.start(mCallback); + mMediaSwitchingController.start(mCallback); - mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView); + mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView); verify(mStarter).startActivity(any(Intent.class), anyBoolean(), Mockito.eq(mController)); @@ -403,11 +406,12 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void tryToLaunchInAppRoutingIntent_componentNameNotNull_startActivity() { when(mDialogTransitionAnimator.createActivityTransitionController(any(View.class))) .thenReturn(mController); - mMediaOutputController.start(mCallback); + mMediaSwitchingController.start(mCallback); when(mLocalMediaManager.getLinkedItemComponentName()).thenReturn( new ComponentName(mPackageName, "")); - mMediaOutputController.tryToLaunchInAppRoutingIntent(TEST_DEVICE_1_ID, mDialogLaunchView); + mMediaSwitchingController.tryToLaunchInAppRoutingIntent( + TEST_DEVICE_1_ID, mDialogLaunchView); verify(mStarter).startActivity(any(Intent.class), anyBoolean(), Mockito.eq(mController)); @@ -415,9 +419,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDevicesUpdated_unregistersNearbyDevicesCallback() throws RemoteException { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - mMediaOutputController.onDevicesUpdated(ImmutableList.of()); + mMediaSwitchingController.onDevicesUpdated(ImmutableList.of()); verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any()); } @@ -425,11 +429,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_withNearbyDevices_updatesRangeInformation() throws RemoteException { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDevicesUpdated(mNearbyDevices); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDevicesUpdated(mNearbyDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); verify(mMediaDevice1).setRangeZone(NearbyDevice.RANGE_FAR); verify(mMediaDevice2).setRangeZone(NearbyDevice.RANGE_CLOSE); @@ -438,11 +442,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_withNearbyDevices_rankByRangeInformation() throws RemoteException { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDevicesUpdated(mNearbyDevices); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDevicesUpdated(mNearbyDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); assertThat(mMediaDevices.get(0).getId()).isEqualTo(TEST_DEVICE_1_ID); } @@ -451,11 +455,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation() throws RemoteException { when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDevicesUpdated(mNearbyDevices); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDevicesUpdated(mNearbyDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); verify(mMediaDevice1, never()).setRangeZone(anyInt()); verify(mMediaDevice2, never()).setRangeZone(anyInt()); @@ -463,7 +467,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_verifyDeviceListCallback() { - // This test relies on mMediaOutputController.start being called while the selected device + // This test relies on mMediaSwitchingController.start being called while the selected + // device // list has exactly one item, and that item's id is: // - Different from both ids in mMediaDevices. // - Different from the id of the route published by the device under test (usually the @@ -475,12 +480,12 @@ public class MediaOutputControllerTest extends SysuiTestCase { .when(mLocalMediaManager) .getSelectedMediaDevice(); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); final List<MediaDevice> devices = new ArrayList<>(); - for (MediaItem item : mMediaOutputController.getMediaItemList()) { + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { if (item.getMediaDevice().isPresent()) { devices.add(item.getMediaDevice().get()); } @@ -488,14 +493,15 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(devices.containsAll(mMediaDevices)).isTrue(); assertThat(devices.size()).isEqualTo(mMediaDevices.size()); - assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo( - mMediaDevices.size() + 2); + assertThat(mMediaSwitchingController.getMediaItemList().size()) + .isEqualTo(mMediaDevices.size() + 2); verify(mCb).onDeviceListChanged(); } @Test public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() { - // This test relies on mMediaOutputController.start being called while the selected device + // This test relies on mMediaSwitchingController.start being called while the selected + // device // list has exactly one item, and that item's id is: // - Different from both ids in mMediaDevices. // - Different from the id of the route published by the device under test (usually the @@ -510,12 +516,12 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice1.getFeatures()).thenReturn( ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); final List<MediaDevice> devices = new ArrayList<>(); - for (MediaItem item : mMediaOutputController.getMediaItemList()) { + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { if (item.getMediaDevice().isPresent()) { devices.add(item.getMediaDevice().get()); } @@ -523,23 +529,72 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(devices.containsAll(mMediaDevices)).isTrue(); assertThat(devices.size()).isEqualTo(mMediaDevices.size()); - assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo( - mMediaDevices.size() + 1); + assertThat(mMediaSwitchingController.getMediaItemList().size()) + .isEqualTo(mMediaDevices.size() + 1); verify(mCb).onDeviceListChanged(); } + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) + @Test + public void onInputDeviceListUpdate_verifyDeviceListCallback() { + AudioDeviceInfo[] audioDeviceInfos = {}; + when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) + .thenReturn(audioDeviceInfos); + mMediaSwitchingController.start(mCb); + + // Output devices have changed. + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + final int MAX_VOLUME = 1; + final int CURRENT_VOLUME = 0; + final boolean IS_VOLUME_FIXED = true; + final MediaDevice mediaDevice3 = + InputMediaDevice.create( + mContext, + TEST_DEVICE_3_ID, + AudioDeviceInfo.TYPE_BUILTIN_MIC, + MAX_VOLUME, + CURRENT_VOLUME, + IS_VOLUME_FIXED); + final MediaDevice mediaDevice4 = + InputMediaDevice.create( + mContext, + TEST_DEVICE_4_ID, + AudioDeviceInfo.TYPE_WIRED_HEADSET, + MAX_VOLUME, + CURRENT_VOLUME, + IS_VOLUME_FIXED); + final List<MediaDevice> inputDevices = new ArrayList<>(); + inputDevices.add(mediaDevice3); + inputDevices.add(mediaDevice4); + + // Input devices have changed. + mMediaSwitchingController.mInputDeviceCallback.onInputDeviceListUpdated(inputDevices); + + final List<MediaDevice> devices = new ArrayList<>(); + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { + if (item.getMediaDevice().isPresent()) { + devices.add(item.getMediaDevice().get()); + } + } + + assertThat(devices).containsAtLeastElementsIn(mMediaDevices); + assertThat(devices).hasSize(mMediaDevices.size() + inputDevices.size()); + verify(mCb, atLeastOnce()).onDeviceListChanged(); + } + @Test public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() { when(mMediaDevice1.isSuggestedDevice()).thenReturn(true); when(mMediaDevice2.isSuggestedDevice()).thenReturn(false); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.getMediaItemList().clear(); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); final List<MediaDevice> devices = new ArrayList<>(); int dividerSize = 0; - for (MediaItem item : mMediaOutputController.getMediaItemList()) { + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { if (item.getMediaDevice().isPresent()) { devices.add(item.getMediaDevice().get()); } @@ -556,33 +611,33 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.mIsRefreshing = true; + mMediaSwitchingController.mIsRefreshing = true; - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.mNeedRefresh).isTrue(); + assertThat(mMediaSwitchingController.mNeedRefresh).isTrue(); } @Test public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.mIsRefreshing = true; + mMediaSwitchingController.mIsRefreshing = true; - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.mNeedRefresh).isTrue(); + assertThat(mMediaSwitchingController.mNeedRefresh).isTrue(); } @Test public void cancelMuteAwaitConnection_cancelsWithMediaManager() { when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class)); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.cancelMuteAwaitConnection(); + mMediaSwitchingController.cancelMuteAwaitConnection(); verify(mAudioManager).cancelMuteAwaitConnection(any()); } @@ -590,17 +645,17 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() { when(mAudioManager.getMutingExpectedDevice()).thenReturn(null); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.cancelMuteAwaitConnection(); + mMediaSwitchingController.cancelMuteAwaitConnection(); verify(mAudioManager, never()).cancelMuteAwaitConnection(any()); } @Test public void getAppSourceName_packageNameIsNull_returnsNull() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, "", mSpyContext.getUser(), @@ -617,25 +672,25 @@ public class MediaOutputControllerTest extends SysuiTestCase { mFlags, mVolumePanelGlobalStateInteractor, mUserTracker); - testMediaOutputController.start(mCb); + testMediaSwitchingController.start(mCb); reset(mCb); - testMediaOutputController.getAppSourceName(); + testMediaSwitchingController.getAppSourceName(); - assertThat(testMediaOutputController.getAppSourceName()).isNull(); + assertThat(testMediaSwitchingController.getAppSourceName()).isNull(); } @Test public void isActiveItem_deviceNotConnected_returnsFalse() { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); - assertThat(mMediaOutputController.isActiveItem(mMediaDevice1)).isFalse(); + assertThat(mMediaSwitchingController.isActiveItem(mMediaDevice1)).isFalse(); } @Test public void getNotificationSmallIcon_packageNameIsNull_returnsNull() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, "", mSpyContext.getUser(), @@ -652,23 +707,23 @@ public class MediaOutputControllerTest extends SysuiTestCase { mFlags, mVolumePanelGlobalStateInteractor, mUserTracker); - testMediaOutputController.start(mCb); + testMediaSwitchingController.start(mCb); reset(mCb); - testMediaOutputController.getAppSourceName(); + testMediaSwitchingController.getAppSourceName(); - assertThat(testMediaOutputController.getNotificationSmallIcon()).isNull(); + assertThat(testMediaSwitchingController.getNotificationSmallIcon()).isNull(); } @Test public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.mNeedRefresh = true; + mMediaSwitchingController.mNeedRefresh = true; - mMediaOutputController.refreshDataSetIfNeeded(); + mMediaSwitchingController.refreshDataSetIfNeeded(); - assertThat(mMediaOutputController.mNeedRefresh).isFalse(); + assertThat(mMediaSwitchingController.mNeedRefresh).isFalse(); } @Test @@ -677,13 +732,13 @@ public class MediaOutputControllerTest extends SysuiTestCase { ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); - assertThat(mMediaOutputController.isCurrentConnectedDeviceRemote()).isTrue(); + assertThat(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).isTrue(); } @Test public void addDeviceToPlayMedia_callsLocalMediaManager() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -702,16 +757,16 @@ public class MediaOutputControllerTest extends SysuiTestCase { mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); - testMediaOutputController.mLocalMediaManager = mockLocalMediaManager; + testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager; - testMediaOutputController.addDeviceToPlayMedia(mMediaDevice2); + testMediaSwitchingController.addDeviceToPlayMedia(mMediaDevice2); verify(mockLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2); } @Test public void removeDeviceFromPlayMedia_callsLocalMediaManager() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -730,15 +785,15 @@ public class MediaOutputControllerTest extends SysuiTestCase { mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); - testMediaOutputController.mLocalMediaManager = mockLocalMediaManager; + testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager; - testMediaOutputController.removeDeviceFromPlayMedia(mMediaDevice2); + testMediaSwitchingController.removeDeviceFromPlayMedia(mMediaDevice2); verify(mockLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2); } @Test public void getDeselectableMediaDevice_triggersFromLocalMediaManager() { - mMediaOutputController.getDeselectableMediaDevice(); + mMediaSwitchingController.getDeselectableMediaDevice(); verify(mLocalMediaManager).getDeselectableMediaDevice(); } @@ -746,108 +801,108 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() { int testVolume = 10; - mMediaOutputController.adjustSessionVolume(testVolume); + mMediaSwitchingController.adjustSessionVolume(testVolume); verify(mLocalMediaManager).adjustSessionVolume(testVolume); } @Test public void logInteractionAdjustVolume_triggersFromMetricLogger() { - MediaOutputMetricLogger spyMediaOutputMetricLogger = spy( - mMediaOutputController.mMetricLogger); - mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger; + MediaOutputMetricLogger spyMediaOutputMetricLogger = + spy(mMediaSwitchingController.mMetricLogger); + mMediaSwitchingController.mMetricLogger = spyMediaOutputMetricLogger; - mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1); + mMediaSwitchingController.logInteractionAdjustVolume(mMediaDevice1); verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1); } @Test public void getSessionVolumeMax_triggersFromLocalMediaManager() { - mMediaOutputController.getSessionVolumeMax(); + mMediaSwitchingController.getSessionVolumeMax(); verify(mLocalMediaManager).getSessionVolumeMax(); } @Test public void getSessionVolume_triggersFromLocalMediaManager() { - mMediaOutputController.getSessionVolume(); + mMediaSwitchingController.getSessionVolume(); verify(mLocalMediaManager).getSessionVolume(); } @Test public void getSessionName_triggersFromLocalMediaManager() { - mMediaOutputController.getSessionName(); + mMediaSwitchingController.getSessionName(); verify(mLocalMediaManager).getSessionName(); } @Test public void releaseSession_triggersFromLocalMediaManager() { - mMediaOutputController.releaseSession(); + mMediaSwitchingController.releaseSession(); verify(mLocalMediaManager).releaseSession(); } @Test public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.isAnyDeviceTransferring()).isFalse(); + assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isFalse(); } @Test public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() { when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue(); + assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue(); } @Test public void isAnyDeviceTransferring_advancedLayoutSupport() { when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); - mMediaOutputController.start(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.start(mCb); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue(); + assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue(); } @Test public void isPlaying_stateIsNull() { when(mSessionMediaController.getPlaybackState()).thenReturn(null); - assertThat(mMediaOutputController.isPlaying()).isFalse(); + assertThat(mMediaSwitchingController.isPlaying()).isFalse(); } @Test public void onSelectedDeviceStateChanged_verifyCallback() { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.connectDevice(mMediaDevice1); + mMediaSwitchingController.connectDevice(mMediaDevice1); - mMediaOutputController.onSelectedDeviceStateChanged(mMediaDevice1, - LocalMediaManager.MediaDeviceState.STATE_CONNECTED); + mMediaSwitchingController.onSelectedDeviceStateChanged( + mMediaDevice1, LocalMediaManager.MediaDeviceState.STATE_CONNECTED); verify(mCb).onRouteChanged(); } @Test public void onDeviceAttributesChanged_verifyCallback() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceAttributesChanged(); + mMediaSwitchingController.onDeviceAttributesChanged(); verify(mCb).onRouteChanged(); } @@ -855,11 +910,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onRequestFailed_verifyCallback() { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.connectDevice(mMediaDevice2); + mMediaSwitchingController.connectDevice(mMediaDevice2); - mMediaOutputController.onRequestFailed(0 /* reason */); + mMediaSwitchingController.onRequestFailed(0 /* reason */); verify(mCb, atLeastOnce()).onRouteChanged(); } @@ -868,37 +923,40 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void getHeaderTitle_withoutMetadata_returnDefaultString() { when(mSessionMediaController.getMetadata()).thenReturn(null); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo( - mContext.getText(R.string.controls_media_title)); + assertThat( + mMediaSwitchingController + .getHeaderTitle() + .equals(mContext.getText(R.string.controls_media_title))) + .isTrue(); } @Test public void getHeaderTitle_withMetadata_returnSongName() { when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo(TEST_SONG); + assertThat(mMediaSwitchingController.getHeaderTitle().equals(TEST_SONG)).isTrue(); } @Test public void getHeaderSubTitle_withoutMetadata_returnNull() { when(mSessionMediaController.getMetadata()).thenReturn(null); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderSubTitle()).isNull(); + assertThat(mMediaSwitchingController.getHeaderSubTitle()).isNull(); } @Test public void getHeaderSubTitle_withMetadata_returnArtistName() { when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderSubTitle()).isEqualTo(TEST_ARTIST); + assertThat(mMediaSwitchingController.getHeaderSubTitle().equals(TEST_ARTIST)).isTrue(); } @Test @@ -911,8 +969,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { mRoutingSessionInfos.add(mRemoteSessionInfo); when(mLocalMediaManager.getRemoteRoutingSessions()).thenReturn(mRoutingSessionInfos); - assertThat(mMediaOutputController.getActiveRemoteMediaDevices()).containsExactly( - mRemoteSessionInfo); + assertThat(mMediaSwitchingController.getActiveRemoteMediaDevices()) + .containsExactly(mRemoteSessionInfo); } @Test @@ -933,7 +991,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice2); doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice(); doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice(); - final List<MediaDevice> groupMediaDevices = mMediaOutputController.getGroupMediaDevices(); + final List<MediaDevice> groupMediaDevices = + mMediaSwitchingController.getGroupMediaDevices(); // Reset order selectedMediaDevices.clear(); selectedMediaDevices.add(selectedMediaDevice2); @@ -941,7 +1000,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.clear(); selectableMediaDevices.add(selectableMediaDevice2); selectableMediaDevices.add(selectableMediaDevice1); - final List<MediaDevice> newDevices = mMediaOutputController.getGroupMediaDevices(); + final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices(); assertThat(newDevices.size()).isEqualTo(groupMediaDevices.size()); for (int i = 0; i < groupMediaDevices.size(); i++) { @@ -970,7 +1029,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice2); doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice(); doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice(); - final List<MediaDevice> groupMediaDevices = mMediaOutputController.getGroupMediaDevices(); + final List<MediaDevice> groupMediaDevices = + mMediaSwitchingController.getGroupMediaDevices(); // Reset order selectedMediaDevices.clear(); selectedMediaDevices.add(selectedMediaDevice2); @@ -979,7 +1039,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice3); selectableMediaDevices.add(selectableMediaDevice2); selectableMediaDevices.add(selectableMediaDevice1); - final List<MediaDevice> newDevices = mMediaOutputController.getGroupMediaDevices(); + final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices(); assertThat(newDevices.size()).isEqualTo(5); for (int i = 0; i < groupMediaDevices.size(); i++) { @@ -991,8 +1051,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void getNotificationLargeIcon_withoutPackageName_returnsNull() { - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -1010,7 +1070,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - assertThat(mMediaOutputController.getNotificationIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationIcon()).isNull(); } @Test @@ -1028,7 +1088,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getLargeIcon()).thenReturn(null); - assertThat(mMediaOutputController.getNotificationIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationIcon()).isNull(); } @Test @@ -1047,7 +1107,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getLargeIcon()).thenReturn(icon); - assertThat(mMediaOutputController.getNotificationIcon()).isInstanceOf(IconCompat.class); + assertThat(mMediaSwitchingController.getNotificationIcon()).isInstanceOf(IconCompat.class); } @Test @@ -1066,7 +1126,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(false); when(notification.getLargeIcon()).thenReturn(icon); - assertThat(mMediaOutputController.getNotificationIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationIcon()).isNull(); } @Test @@ -1084,7 +1144,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getSmallIcon()).thenReturn(null); - assertThat(mMediaOutputController.getNotificationSmallIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationSmallIcon()).isNull(); } @Test @@ -1103,8 +1163,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getSmallIcon()).thenReturn(icon); - assertThat(mMediaOutputController.getNotificationSmallIcon()).isInstanceOf( - IconCompat.class); + assertThat(mMediaSwitchingController.getNotificationSmallIcon()) + .isInstanceOf(IconCompat.class); } @Test @@ -1112,8 +1172,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); when(mMediaDevice1.getIcon()).thenReturn(mDrawable); - assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf( - IconCompat.class); + assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)) + .isInstanceOf(IconCompat.class); } @Test @@ -1121,13 +1181,13 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); when(mMediaDevice1.getIcon()).thenReturn(null); - assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf( - IconCompat.class); + assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)) + .isInstanceOf(IconCompat.class); } @Test public void setColorFilter_setColorFilterToDrawable() { - mMediaOutputController.setColorFilter(mDrawable, true); + mMediaSwitchingController.setColorFilter(mDrawable, true); verify(mDrawable).setColorFilter(any(PorterDuffColorFilter.class)); } @@ -1150,11 +1210,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice2); doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice(); doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice(); - assertThat(mMediaOutputController.getGroupMediaDevices().isEmpty()).isFalse(); + assertThat(mMediaSwitchingController.getGroupMediaDevices().isEmpty()).isFalse(); - mMediaOutputController.resetGroupMediaDevices(); + mMediaSwitchingController.resetGroupMediaDevices(); - assertThat(mMediaOutputController.mGroupMediaDevices.isEmpty()).isTrue(); + assertThat(mMediaSwitchingController.mGroupMediaDevices.isEmpty()).isTrue(); } @Test @@ -1164,7 +1224,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice1.isVolumeFixed()).thenReturn(true); - assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isFalse(); + assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isFalse(); } @Test @@ -1174,7 +1234,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice1.isVolumeFixed()).thenReturn(false); - assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isTrue(); + assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isTrue(); } @Test @@ -1187,7 +1247,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice2.getDeviceType()).thenReturn( MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); - mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); + mMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(), anyLong()); @@ -1195,8 +1255,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -1214,7 +1274,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); + testMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); verify(mPowerExemptionManager, never()).addToTemporaryAllowList(anyString(), anyInt(), anyString(), @@ -1223,22 +1283,22 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onMetadataChanged_triggersOnMetadataChanged() { - mMediaOutputController.mCallback = this.mCallback; + mMediaSwitchingController.mCallback = this.mCallback; - mMediaOutputController.mCb.onMetadataChanged(mMediaMetadata); + mMediaSwitchingController.mCb.onMetadataChanged(mMediaMetadata); - verify(mMediaOutputController.mCallback).onMediaChanged(); + verify(mMediaSwitchingController.mCallback).onMediaChanged(); } @Test public void onPlaybackStateChanged_updateWithNullState_onMediaStoppedOrPaused() { when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING); - mMediaOutputController.mCallback = this.mCallback; - mMediaOutputController.start(mCb); + mMediaSwitchingController.mCallback = this.mCallback; + mMediaSwitchingController.start(mCb); - mMediaOutputController.mCb.onPlaybackStateChanged(null); + mMediaSwitchingController.mCb.onPlaybackStateChanged(null); - verify(mMediaOutputController.mCallback).onMediaStoppedOrPaused(); + verify(mMediaSwitchingController.mCallback).onMediaStoppedOrPaused(); } @Test @@ -1246,9 +1306,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mDialogTransitionAnimator.createActivityTransitionController(mDialogLaunchView)) .thenReturn(mActivityTransitionAnimatorController); when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); - mMediaOutputController.mCallback = this.mCallback; + mMediaSwitchingController.mCallback = this.mCallback; - mMediaOutputController.launchBluetoothPairing(mDialogLaunchView); + mMediaSwitchingController.launchBluetoothPairing(mDialogLaunchView); verify(mCallback).dismissDialog(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index af043093b6f7..c710c56fd516 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -168,7 +168,7 @@ import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; -import com.android.systemui.statusbar.core.StatusBarInitializer; +import com.android.systemui.statusbar.core.StatusBarInitializerImpl; import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -504,7 +504,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mock(FragmentService.class), mLightBarController, mAutoHideController, - new StatusBarInitializer( + new StatusBarInitializerImpl( mStatusBarWindowController, mCollapsedStatusBarFragmentProvider, emptySet()), diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 1e2648b228f3..ecc7909a857a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -20,7 +20,6 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; -import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST; @@ -51,7 +50,6 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.SystemClock; -import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; @@ -285,23 +283,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() { - // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows - mDialog.addSliderHapticsToRows(); - - // WHEN haptics try to be delivered to a volume stream - boolean canDeliverHaptics = - mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50); - - // THEN the result is that haptics are not successfully delivered - assertFalse(canDeliverHaptics); - } - - @Test - @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() { - // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows + public void addSliderHaptics_canDeliverOnProgressChangedHaptics() { + // GIVEN that the slider haptics are added to rows mDialog.addSliderHapticsToRows(); // WHEN haptics try to be delivered to a volume stream diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt index 649e4e8a6f7e..1b1d8c5d0f63 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt @@ -25,6 +25,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer +import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -34,9 +36,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.StateFlow val Kosmos.bouncerUserActionsViewModel by Fixture { - BouncerUserActionsViewModel( - bouncerInteractor = bouncerInteractor, - ) + BouncerUserActionsViewModel(bouncerInteractor = bouncerInteractor) } val Kosmos.bouncerUserActionsViewModelFactory by Fixture { @@ -59,6 +59,7 @@ val Kosmos.bouncerSceneContentViewModel by Fixture { pinViewModelFactory = pinBouncerViewModelFactory, patternViewModelFactory = patternBouncerViewModelFactory, passwordViewModelFactory = passwordBouncerViewModelFactory, + bouncerHapticPlayer = bouncerHapticPlayer, ) } @@ -76,6 +77,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture { isInputEnabled: StateFlow<Boolean>, onIntentionalUserInput: () -> Unit, authenticationMethod: AuthenticationMethodModel, + bouncerHapticPlayer: BouncerHapticPlayer, ): PinBouncerViewModel { return PinBouncerViewModel( applicationContext = applicationContext, @@ -84,6 +86,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture { isInputEnabled = isInputEnabled, onIntentionalUserInput = onIntentionalUserInput, authenticationMethod = authenticationMethod, + bouncerHapticPlayer = bouncerHapticPlayer, ) } } @@ -92,6 +95,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture { val Kosmos.patternBouncerViewModelFactory by Fixture { object : PatternBouncerViewModel.Factory { override fun create( + bouncerHapticPlayer: BouncerHapticPlayer, isInputEnabled: StateFlow<Boolean>, onIntentionalUserInput: () -> Unit, ): PatternBouncerViewModel { @@ -100,6 +104,7 @@ val Kosmos.patternBouncerViewModelFactory by Fixture { interactor = bouncerInteractor, isInputEnabled = isInputEnabled, onIntentionalUserInput = onIntentionalUserInput, + bouncerHapticPlayer = bouncerHapticPlayer, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt index 1ed10fbe94c3..8922b2f5c5ef 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.deviceentry.data.repository.deviceEntryRepository import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.flags.systemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.trustInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -37,5 +38,6 @@ val Kosmos.deviceUnlockedInteractor by Fixture { powerInteractor = powerInteractor, biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, systemPropertiesHelper = fakeSystemPropertiesHelper, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt index c252924f4d2d..c0152b26d7a3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import android.platform.test.annotations.EnableFlags -import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR @@ -31,7 +30,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER * that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites. */ @EnableFlags( - FLAG_COMPOSE_LOCKSCREEN, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt index 5ad973a54252..2b81da33b9bc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt @@ -20,8 +20,10 @@ import com.google.android.msdl.data.model.FeedbackLevel import com.google.android.msdl.data.model.MSDLToken import com.google.android.msdl.domain.InteractionProperties import com.google.android.msdl.domain.MSDLPlayer +import com.google.android.msdl.logging.MSDLEvent class FakeMSDLPlayer : MSDLPlayer { + private val history = arrayListOf<MSDLEvent>() var currentFeedbackLevel = FeedbackLevel.DEFAULT var latestTokenPlayed: MSDLToken? = null private set @@ -34,5 +36,8 @@ class FakeMSDLPlayer : MSDLPlayer { override fun playToken(token: MSDLToken, properties: InteractionProperties?) { latestTokenPlayed = token latestPropertiesPlayed = properties + history.add(MSDLEvent(token, properties)) } + + override fun getHistory(): List<MSDLEvent> = history } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt index ca748b661d04..80db1e9b5e29 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.haptics.qs +import com.android.systemui.classifier.fakeFalsingManager import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.Kosmos import com.android.systemui.log.core.FakeLogBuffer @@ -26,6 +27,7 @@ val Kosmos.qsLongPressEffect by QSLongPressEffect( vibratorHelper, keyguardStateController, + fakeFalsingManager, FakeLogBuffer.Factory.create(), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt index c416ea1c1b39..91602c23ac17 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt @@ -16,8 +16,13 @@ package com.android.systemui.statusbar.data.repository +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf class FakeRemoteInputRepository : RemoteInputRepository { override val isRemoteInputActive = MutableStateFlow(false) + override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null) + + override fun setRemoteInputRowBottomBound(bottom: Float?) {} } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt index 6370a5d9c80b..7244d465ed7e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor val Kosmos.notificationScrollViewModel by Fixture { @@ -29,6 +30,7 @@ val Kosmos.notificationScrollViewModel by Fixture { dumpManager = dumpManager, stackAppearanceInteractor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, + remoteInputInteractor = remoteInputInteractor, sceneInteractor = sceneInteractor, keyguardInteractor = { keyguardInteractor }, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index 8bfc390ecfa3..e5cf0a90ebbd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor @@ -31,6 +32,7 @@ val Kosmos.notificationsPlaceholderViewModel by Fixture { sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, headsUpNotificationInteractor = headsUpNotificationInteractor, + remoteInputInteractor = remoteInputInteractor, featureFlags = featureFlagsClassic, dumpManager = dumpManager, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt index 61b53c9a2067..99cd8309631e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt @@ -22,6 +22,8 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository +import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository +import com.android.systemui.statusbar.policy.data.repository.userSetupRepository import com.android.systemui.statusbar.policy.data.repository.zenModeRepository val Kosmos.zenModeInteractor by Fixture { @@ -31,5 +33,7 @@ val Kosmos.zenModeInteractor by Fixture { notificationSettingsRepository = notificationSettingsRepository, bgDispatcher = testDispatcher, iconLoader = zenIconLoader, + deviceProvisioningRepository = deviceProvisioningRepository, + userSetupRepository = userSetupRepository, ) } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index a10097427ae5..57b58d8741da 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -128,7 +128,11 @@ constructor( val currentDirection = if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING - if (isTransitionInProgress && currentDirection != lastFoldUpdate) { + val changedDirectionWhileInTransition = + isTransitionInProgress && currentDirection != lastFoldUpdate + val unfoldedPastThresholdSinceLastTransition = + angle - lastHingeAngleBeforeTransition > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES + if (changedDirectionWhileInTransition || unfoldedPastThresholdSinceLastTransition) { lastHingeAngleBeforeTransition = lastHingeAngle } @@ -153,7 +157,7 @@ constructor( isOnLargeScreen // Avoids sending closing event when on small screen. // Start event is sent regardless due to hall sensor. ) { - notifyFoldUpdate(transitionUpdate, lastHingeAngle) + notifyFoldUpdate(transitionUpdate, angle) } if (isTransitionInProgress) { diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index d1a3bf9b529f..10e4f3820cd7 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -343,6 +343,8 @@ android_ravenwood_libgroup { data: [ ":framework-res", ":ravenwood-empty-res", + ":framework-platform-compat-config", + ":services-platform-compat-config", ], libs: [ "100-framework-minus-apex.ravenwood", diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 4cb2ce1bdfd7..5d251bdafd44 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -195,23 +195,25 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable try { performGlobalInitialization(); - mTestClass = new TestClass(testClass); - - Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); - - onRunnerInitializing(); - /* * If the class has @DisabledOnRavenwood, then we'll delegate to * ClassSkippingTestRunner, which simply skips it. + * + * We need to do it before instantiating TestClass for b/367694651. */ if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood( - mTestClass.getJavaClass())) { - mRealRunner = new ClassSkippingTestRunner(mTestClass); + testClass)) { + mRealRunner = new ClassSkippingTestRunner(testClass); mDescription = mRealRunner.getDescription(); return; } + mTestClass = new TestClass(testClass); + + Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); + + onRunnerInitializing(); + // Find the real runner. final Class<? extends Runner> realRunnerClass; final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class); @@ -444,14 +446,11 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable * filter. */ private static class ClassSkippingTestRunner extends Runner implements Filterable { - private final TestClass mTestClass; private final Description mDescription; private boolean mFilteredOut; - ClassSkippingTestRunner(TestClass testClass) { - mTestClass = testClass; - mDescription = Description.createTestDescription( - testClass.getJavaClass(), testClass.getJavaClass().getSimpleName()); + ClassSkippingTestRunner(Class<?> testClass) { + mDescription = Description.createTestDescription(testClass, testClass.getSimpleName()); mFilteredOut = false; } diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java index 6d8fb983504b..09ed12d49cea 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java @@ -17,12 +17,14 @@ package com.android.ravenwoodtest.runnercallbacktests; import static org.junit.Assume.assumeTrue; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.NoRavenizer; import android.platform.test.ravenwood.RavenwoodAwareTestRunner; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -353,4 +355,34 @@ public class RavenwoodRunnerCallbackTest extends RavenwoodRunnerTestBase { public void test2() { } } + + /** + * The test class is unloadable, but has a @DisabledOnRavenwood. + */ + @RunWith(AndroidJUnit4.class) + @DisabledOnRavenwood + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testSuiteStarted: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) + testIgnored: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) + testSuiteFinished: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) + testSuiteFinished: classes + testRunFinished: 0,0,0,1 + """) + // CHECKSTYLE:ON + public static class ClassUnloadbleTest { + static { + Assert.fail("Class unloadable!"); + } + + @Test + public void test1() { + } + + @Test + public void test2() { + } + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java index c3b7087a44c3..1f98334bb8ce 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java @@ -16,7 +16,15 @@ package com.android.server.appfunctions; +import android.annotation.NonNull; +import android.os.UserHandle; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -33,5 +41,50 @@ public final class AppFunctionExecutors { /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>()); + /** A map of per-user executors for queued work. */ + @GuardedBy("sLock") + private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>(); + + private static final Object sLock = new Object(); + + /** + * Returns a per-user executor for queued metadata sync request. + * + * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence + * the use of a single thread. + * + * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code + * MetadataSyncAdapter}. + */ + // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself. + public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) { + synchronized (sLock) { + ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null); + if (executor == null) { + executor = Executors.newSingleThreadExecutor(); + mPerUserExecutorsLocked.put(user.getIdentifier(), executor); + } + return executor; + } + } + + /** + * Shuts down and removes the per-user executor for queued work. + * + * <p>This should be called when the user is removed. + */ + public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user) + throws InterruptedException { + ExecutorService executor; + synchronized (sLock) { + executor = mPerUserExecutorsLocked.get(user.getIdentifier()); + mPerUserExecutorsLocked.remove(user.getIdentifier()); + } + if (executor != null) { + executor.shutdown(); + var unused = executor.awaitTermination(30, TimeUnit.SECONDS); + } + } + private AppFunctionExecutors() {} } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java index 02800cbf4f3d..c293087defb6 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java @@ -16,6 +16,7 @@ package com.android.server.appfunctions; +import android.annotation.NonNull; import android.app.appfunctions.AppFunctionManagerConfiguration; import android.content.Context; @@ -36,4 +37,14 @@ public class AppFunctionManagerService extends SystemService { publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl); } } + + @Override + public void onUserUnlocked(@NonNull TargetUser user) { + mServiceImpl.onUserUnlocked(user); + } + + @Override + public void onUserStopping(@NonNull TargetUser user) { + mServiceImpl.onUserStopping(user); + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 2362b91c826e..cf039df3bc96 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -19,29 +19,35 @@ package com.android.server.appfunctions; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; import android.app.appfunctions.IExecuteAppFunctionCallback; import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback; +import android.app.appsearch.AppSearchManager; +import android.app.appsearch.AppSearchResult; +import android.app.appsearch.observer.DocumentChangeInfo; +import android.app.appsearch.observer.ObserverCallback; +import android.app.appsearch.observer.ObserverSpec; +import android.app.appsearch.observer.SchemaChangeInfo; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; import android.util.Slog; -import android.app.appsearch.AppSearchResult; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemService.TargetUser; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; +import java.io.IOException; import java.util.Objects; import java.util.concurrent.CompletionException; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; /** Implementation of the AppFunctionManagerService. */ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @@ -51,9 +57,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final CallerValidator mCallerValidator; private final ServiceHelper mInternalServiceHelper; private final ServiceConfig mServiceConfig; + private final Context mContext; public AppFunctionManagerServiceImpl(@NonNull Context context) { this( + context, new RemoteServiceCallerImpl<>( context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR), new CallerValidatorImpl(context), @@ -63,10 +71,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @VisibleForTesting AppFunctionManagerServiceImpl( + Context context, RemoteServiceCaller<IAppFunctionService> remoteServiceCaller, CallerValidator callerValidator, ServiceHelper appFunctionInternalServiceHelper, ServiceConfig serviceConfig) { + mContext = Objects.requireNonNull(context); mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller); mCallerValidator = Objects.requireNonNull(callerValidator); mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper); @@ -90,6 +100,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } } + /** Called when the user is unlocked. */ + public void onUserUnlocked(TargetUser user) { + Objects.requireNonNull(user); + + registerAppSearchObserver(user); + trySyncRuntimeMetadata(user); + } + + /** Called when the user is stopping. */ + public void onUserStopping(@NonNull TargetUser user) { + Objects.requireNonNull(user); + + try { + AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle()); + MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); + } catch (InterruptedException e) { + Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e); + } + } + private void executeAppFunctionInternal( ExecuteAppFunctionAidlRequest requestInternal, SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { @@ -132,53 +162,55 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } - var unused = mCallerValidator - .verifyCallerCanExecuteAppFunction( - validatedCallingPackage, - targetPackageName, - requestInternal.getClientRequest().getFunctionIdentifier()) - .thenAccept( - canExecute -> { - if (!canExecute) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_DENIED, - "Caller does not have permission to execute the" - + " appfunction", - /* extras= */ null)); - return; - } - Intent serviceIntent = - mInternalServiceHelper.resolveAppFunctionService( - targetPackageName, targetUser); - if (serviceIntent == null) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, - "Cannot find the target service.", - /* extras= */ null)); - return; - } - final long token = Binder.clearCallingIdentity(); - try { - bindAppFunctionServiceUnchecked( - requestInternal, - serviceIntent, - targetUser, - safeExecuteAppFunctionCallback, - /* bindFlags= */ Context.BIND_AUTO_CREATE, - /* timeoutInMillis= */ mServiceConfig - .getExecuteAppFunctionTimeoutMillis()); - } finally { - Binder.restoreCallingIdentity(token); - } - }) - .exceptionally( - ex -> { - safeExecuteAppFunctionCallback.onResult( - mapExceptionToExecuteAppFunctionResponse(ex)); - return null; - }); + var unused = + mCallerValidator + .verifyCallerCanExecuteAppFunction( + validatedCallingPackage, + targetPackageName, + requestInternal.getClientRequest().getFunctionIdentifier()) + .thenAccept( + canExecute -> { + if (!canExecute) { + safeExecuteAppFunctionCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse.RESULT_DENIED, + "Caller does not have permission to execute" + + " the appfunction", + /* extras= */ null)); + return; + } + Intent serviceIntent = + mInternalServiceHelper.resolveAppFunctionService( + targetPackageName, targetUser); + if (serviceIntent == null) { + safeExecuteAppFunctionCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse + .RESULT_INTERNAL_ERROR, + "Cannot find the target service.", + /* extras= */ null)); + return; + } + final long token = Binder.clearCallingIdentity(); + try { + bindAppFunctionServiceUnchecked( + requestInternal, + serviceIntent, + targetUser, + safeExecuteAppFunctionCallback, + /* bindFlags= */ Context.BIND_AUTO_CREATE, + /* timeoutInMillis= */ mServiceConfig + .getExecuteAppFunctionTimeoutMillis()); + } finally { + Binder.restoreCallingIdentity(token); + } + }) + .exceptionally( + ex -> { + safeExecuteAppFunctionCallback.onResult( + mapExceptionToExecuteAppFunctionResponse(ex)); + return null; + }); } private void bindAppFunctionServiceUnchecked( @@ -256,7 +288,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) { - if(e instanceof CompletionException) { + if (e instanceof CompletionException) { e = e.getCause(); } @@ -291,4 +323,103 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR; } + + private void registerAppSearchObserver(@NonNull TargetUser user) { + AppSearchManager perUserAppSearchManager = + mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0) + .getSystemService(AppSearchManager.class); + if (perUserAppSearchManager == null) { + Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier()); + return; + } + try (FutureGlobalSearchSession futureGlobalSearchSession = + new FutureGlobalSearchSession( + perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR)) { + AppFunctionMetadataObserver appFunctionMetadataObserver = + new AppFunctionMetadataObserver( + user.getUserHandle(), + mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); + var unused = + futureGlobalSearchSession + .registerObserverCallbackAsync( + "android", + new ObserverSpec.Builder().build(), + THREAD_POOL_EXECUTOR, + appFunctionMetadataObserver) + .whenComplete( + (voidResult, ex) -> { + if (ex != null) { + Slog.e(TAG, "Failed to register observer: ", ex); + } + }); + + } catch (IOException ex) { + Slog.e(TAG, "Failed to close observer session: ", ex); + } + } + + private void trySyncRuntimeMetadata(@NonNull TargetUser user) { + MetadataSyncAdapter metadataSyncAdapter = + MetadataSyncPerUser.getPerUserMetadataSyncAdapter( + user.getUserHandle(), + mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); + if (metadataSyncAdapter != null) { + var unused = + metadataSyncAdapter + .submitSyncRequest() + .whenComplete( + (isSuccess, ex) -> { + if (ex != null || !isSuccess) { + Slog.e(TAG, "Sync was not successful"); + } + }); + } + } + + private static class AppFunctionMetadataObserver implements ObserverCallback { + @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter; + + AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) { + mPerUserMetadataSyncAdapter = + MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext); + } + + @Override + public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) { + if (mPerUserMetadataSyncAdapter == null) { + return; + } + if (documentChangeInfo + .getDatabaseName() + .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) + && documentChangeInfo + .getNamespace() + .equals( + AppFunctionStaticMetadataHelper + .APP_FUNCTION_STATIC_NAMESPACE)) { + var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); + } + } + + @Override + public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) { + if (mPerUserMetadataSyncAdapter == null) { + return; + } + if (schemaChangeInfo + .getDatabaseName() + .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) { + boolean shouldInitiateSync = false; + for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) { + if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) { + shouldInitiateSync = true; + break; + } + } + if (shouldInitiateSync) { + var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); + } + } + } + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index e2573590bf5d..8c6f50e5c1bd 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -24,6 +24,8 @@ import android.annotation.WorkerThread; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appsearch.AppSearchBatchResult; +import android.app.appsearch.AppSearchManager; +import android.app.appsearch.AppSearchManager.SearchContext; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.PackageIdentifier; @@ -61,9 +63,8 @@ import java.util.concurrent.Executor; */ public class MetadataSyncAdapter { private static final String TAG = MetadataSyncAdapter.class.getSimpleName(); - private final FutureAppSearchSession mRuntimeMetadataSearchSession; - private final FutureAppSearchSession mStaticMetadataSearchSession; private final Executor mSyncExecutor; + private final AppSearchManager mAppSearchManager; private final PackageManager mPackageManager; // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility @@ -73,13 +74,11 @@ public class MetadataSyncAdapter { public MetadataSyncAdapter( @NonNull Executor syncExecutor, - @NonNull FutureAppSearchSession runtimeMetadataSearchSession, - @NonNull FutureAppSearchSession staticMetadataSearchSession, - @NonNull PackageManager packageManager) { + @NonNull PackageManager packageManager, + @NonNull AppSearchManager appSearchManager) { mSyncExecutor = Objects.requireNonNull(syncExecutor); - mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession); - mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession); mPackageManager = Objects.requireNonNull(packageManager); + mAppSearchManager = Objects.requireNonNull(appSearchManager); } /** @@ -89,31 +88,54 @@ public class MetadataSyncAdapter { * synchronization was successful. */ public AndroidFuture<Boolean> submitSyncRequest() { + SearchContext staticMetadataSearchContext = + new SearchContext.Builder( + AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) + .build(); + SearchContext runtimeMetadataSearchContext = + new SearchContext.Builder( + AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB) + .build(); AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>(); mSyncExecutor.execute( () -> { - try { - trySyncAppFunctionMetadataBlocking(); + try (FutureAppSearchSession staticMetadataSearchSession = + new FutureAppSearchSessionImpl( + mAppSearchManager, + AppFunctionExecutors.THREAD_POOL_EXECUTOR, + staticMetadataSearchContext); + FutureAppSearchSession runtimeMetadataSearchSession = + new FutureAppSearchSessionImpl( + mAppSearchManager, + AppFunctionExecutors.THREAD_POOL_EXECUTOR, + runtimeMetadataSearchContext)) { + + trySyncAppFunctionMetadataBlocking( + staticMetadataSearchSession, runtimeMetadataSearchSession); settableSyncStatus.complete(true); - } catch (Exception e) { - settableSyncStatus.completeExceptionally(e); + + } catch (Exception ex) { + settableSyncStatus.completeExceptionally(ex); } }); return settableSyncStatus; } @WorkerThread - private void trySyncAppFunctionMetadataBlocking() + @VisibleForTesting + void trySyncAppFunctionMetadataBlocking( + @NonNull FutureAppSearchSession staticMetadataSearchSession, + @NonNull FutureAppSearchSession runtimeMetadataSearchSession) throws ExecutionException, InterruptedException { ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap = getPackageToFunctionIdMap( - mStaticMetadataSearchSession, + staticMetadataSearchSession, AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE, AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID, AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME); ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap = getPackageToFunctionIdMap( - mRuntimeMetadataSearchSession, + runtimeMetadataSearchSession, RUNTIME_SCHEMA_TYPE, AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME); @@ -134,7 +156,7 @@ public class MetadataSyncAdapter { RemoveByDocumentIdRequest removeByDocumentIdRequest = buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap); AppSearchBatchResult<String, Void> removeDocumentBatchResult = - mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); + runtimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); if (!removeDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( removeDocumentBatchResult.getFailures().values()); @@ -144,13 +166,14 @@ public class MetadataSyncAdapter { if (!addedFunctionsDiffMap.isEmpty()) { // TODO(b/357551503): only set schema on package diff SetSchemaRequest addSetSchemaRequest = - buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas); + buildSetSchemaRequestForRuntimeMetadataSchemas( + mPackageManager, appRuntimeMetadataSchemas); Objects.requireNonNull( - mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); + runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); PutDocumentsRequest putDocumentsRequest = buildPutRuntimeMetadataRequest(addedFunctionsDiffMap); AppSearchBatchResult<String, Void> putDocumentBatchResult = - mRuntimeMetadataSearchSession.put(putDocumentsRequest).get(); + runtimeMetadataSearchSession.put(putDocumentsRequest).get(); if (!putDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( putDocumentBatchResult.getFailures().values()); @@ -211,6 +234,7 @@ public class MetadataSyncAdapter { @NonNull private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas( + @NonNull PackageManager packageManager, @NonNull Set<AppSearchSchema> metadataSchemaSet) { Objects.requireNonNull(metadataSchemaSet); SetSchemaRequest.Builder setSchemaRequestBuilder = @@ -220,7 +244,7 @@ public class MetadataSyncAdapter { String packageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema( runtimeMetadataSchema.getSchemaType()); - byte[] packageCert = getCertificate(packageName); + byte[] packageCert = getCertificate(packageManager, packageName); if (packageCert == null) { continue; } @@ -399,13 +423,15 @@ public class MetadataSyncAdapter { /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */ @Nullable - private byte[] getCertificate(@NonNull String packageName) { + private byte[] getCertificate( + @NonNull PackageManager packageManager, @NonNull String packageName) { + Objects.requireNonNull(packageManager); Objects.requireNonNull(packageName); PackageInfo packageInfo; try { packageInfo = Objects.requireNonNull( - mPackageManager.getPackageInfo( + packageManager.getPackageInfo( packageName, PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES)); diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java new file mode 100644 index 000000000000..f421527e72d0 --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java @@ -0,0 +1,80 @@ +/* + * 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.server.appfunctions; + +import android.annotation.Nullable; +import android.app.appsearch.AppSearchManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +/** A Singleton class that manages per-user metadata sync adapters. */ +public final class MetadataSyncPerUser { + private static final String TAG = MetadataSyncPerUser.class.getSimpleName(); + + /** A map of per-user adapter for synchronizing appFunction metadata. */ + @GuardedBy("sLock") + private static final SparseArray<MetadataSyncAdapter> sPerUserMetadataSyncAdapter = + new SparseArray<>(); + + private static final Object sLock = new Object(); + + /** + * Returns the per-user metadata sync adapter for the given user. + * + * @param user The user for which to get the metadata sync adapter. + * @param userContext The user context for the given user. + * @return The metadata sync adapter for the given user. + */ + @Nullable + public static MetadataSyncAdapter getPerUserMetadataSyncAdapter( + UserHandle user, Context userContext) { + synchronized (sLock) { + MetadataSyncAdapter metadataSyncAdapter = + sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null); + if (metadataSyncAdapter == null) { + AppSearchManager perUserAppSearchManager = + userContext.getSystemService(AppSearchManager.class); + PackageManager perUserPackageManager = userContext.getPackageManager(); + if (perUserAppSearchManager != null) { + metadataSyncAdapter = + new MetadataSyncAdapter( + AppFunctionExecutors.getPerUserSyncExecutor(user), + perUserPackageManager, + perUserAppSearchManager); + sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter); + return metadataSyncAdapter; + } + } + return metadataSyncAdapter; + } + } + + /** + * Removes the per-user metadata sync adapter for the given user. + * + * @param user The user for which to remove the metadata sync adapter. + */ + public static void removeUserSyncAdapter(UserHandle user) { + synchronized (sLock) { + sPerUserMetadataSyncAdapter.remove(user.getIdentifier()); + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS index 4fe0592f9075..4b732ac8e5a9 100644 --- a/services/companion/java/com/android/server/companion/virtual/OWNERS +++ b/services/companion/java/com/android/server/companion/virtual/OWNERS @@ -2,7 +2,9 @@ set noparent -marvinramin@google.com vladokom@google.com +marvinramin@google.com +caen@google.com +biswarupp@google.com ogunwale@google.com michaelwr@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 68d0ad265a46..a459ea944008 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -173,6 +173,15 @@ "include-filter": "com.android.server.wm.BackgroundActivityStart*" } ] + }, + { + "name": "CtsOsTestCases", + "file_patterns": ["StorageManagerService\\.java"], + "options": [ + { + "include-filter": "android.os.storage.cts.StorageStatsManagerTest" + } + ] } ] } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index f0cc09f7c232..22ec7904f972 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -73,6 +73,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; import static android.media.audio.Flags.roForegroundAudioControl; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; +import static android.os.Process.THREAD_GROUP_FOREGROUND_WINDOW; import static android.os.Process.THREAD_GROUP_RESTRICTED; import static android.os.Process.THREAD_GROUP_TOP_APP; import static android.os.Process.THREAD_PRIORITY_DISPLAY; @@ -116,6 +117,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; +import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND; @@ -1731,6 +1733,11 @@ public class OomAdjuster { // The recently used non-top visible freeform app. schedGroup = SCHED_GROUP_TOP_APP; mAdjType = "perceptible-freeform-activity"; + } else if ((flags + & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0) { + // Currently the only case is from freeform apps which are not close to top. + schedGroup = SCHED_GROUP_FOREGROUND_WINDOW; + mAdjType = "vis-multi-window-activity"; } foregroundActivities = true; mHasVisibleActivities = true; @@ -3438,6 +3445,9 @@ public class OomAdjuster { case SCHED_GROUP_RESTRICTED: processGroup = THREAD_GROUP_RESTRICTED; break; + case SCHED_GROUP_FOREGROUND_WINDOW: + processGroup = THREAD_GROUP_FOREGROUND_WINDOW; + break; default: processGroup = THREAD_GROUP_DEFAULT; break; diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 21842db590b0..fb1c2e9a1f9d 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -331,7 +331,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) { ProcessRecordNode node = mLastNode[slot].mNext; final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL; - while (node != tail) { + while (node != null && node != tail) { mTmpOomAdjusterArgs.mApp = node.mApp; if (node.mApp == null) { // TODO(b/336178916) - Temporary logging for root causing b/336178916. @@ -365,7 +365,9 @@ public class OomAdjusterModernImpl extends OomAdjuster { } // Save the next before calling callback, since that may change the node.mNext. final ProcessRecordNode next = node.mNext; - callback.accept(mTmpOomAdjusterArgs); + if (mTmpOomAdjusterArgs.mApp != null) { + callback.accept(mTmpOomAdjusterArgs); + } // There are couple of cases: // a) The current node is moved to another slot // - for this case, we'd need to keep using the "next" node. diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index cb918a045ec6..00250b4ef463 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -303,6 +303,9 @@ public final class ProcessList { // Activity manager's version of Process.THREAD_GROUP_TOP_APP // Disambiguate between actual top app and processes bound to the top app static final int SCHED_GROUP_TOP_APP_BOUND = 4; + // Activity manager's version of Process.THREAD_GROUP_FOREGROUND_WINDOW + // The priority is like between default and top-app. + static final int SCHED_GROUP_FOREGROUND_WINDOW = 5; // The minimum number of cached apps we want to be able to keep around, // without empty apps being able to push them out of memory. diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index e145c90fb1c1..55d9c6eac87a 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -533,7 +533,8 @@ public class AudioDeviceBroker { AudioDeviceInfo.TYPE_BLE_SPEAKER, AudioDeviceInfo.TYPE_LINE_ANALOG, AudioDeviceInfo.TYPE_HDMI, - AudioDeviceInfo.TYPE_AUX_LINE + AudioDeviceInfo.TYPE_AUX_LINE, + AudioDeviceInfo.TYPE_BUS }; /*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) { diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java index 88ff7e4103f9..2cadbc58d0f2 100644 --- a/services/core/java/com/android/server/cpu/CpuMonitorService.java +++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java @@ -216,7 +216,7 @@ public final class CpuMonitorService extends SystemService { @Override public void onBootPhase(int phase) { - if (phase != PHASE_BOOT_COMPLETED) { + if (phase != PHASE_BOOT_COMPLETED || mHandler == null) { return; } Slogf.i(TAG, "Stopping periodic cpuset reading on boot complete"); diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index 38eb416ffdd8..ddea285d3564 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -109,7 +109,7 @@ public class DisplayControl { /** * Sets the HDR conversion mode for the device. * - * Returns the system preferred Hdr output type nn case when HDR conversion mode is + * Returns the system preferred HDR output type in case when HDR conversion mode is * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM}. * Returns Hdr::INVALID in other cases. * @hide diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 93bd92614403..acf4db30ba93 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -318,13 +318,16 @@ final class DisplayDeviceInfo { */ public Display.HdrCapabilities hdrCapabilities; + /** When true, all HDR capabilities are hidden from public APIs */ + public boolean isForceSdr; + /** * Indicates whether this display supports Auto Low Latency Mode. */ public boolean allmSupported; /** - * Indicates whether this display suppors Game content type. + * Indicates whether this display supports Game content type. */ public boolean gameContentTypeSupported; @@ -516,6 +519,7 @@ final class DisplayDeviceInfo { || !Arrays.equals(supportedModes, other.supportedModes) || !Arrays.equals(supportedColorModes, other.supportedColorModes) || !Objects.equals(hdrCapabilities, other.hdrCapabilities) + || isForceSdr != other.isForceSdr || allmSupported != other.allmSupported || gameContentTypeSupported != other.gameContentTypeSupported || densityDpi != other.densityDpi @@ -560,6 +564,7 @@ final class DisplayDeviceInfo { colorMode = other.colorMode; supportedColorModes = other.supportedColorModes; hdrCapabilities = other.hdrCapabilities; + isForceSdr = other.isForceSdr; allmSupported = other.allmSupported; gameContentTypeSupported = other.gameContentTypeSupported; densityDpi = other.densityDpi; @@ -603,6 +608,7 @@ final class DisplayDeviceInfo { sb.append(", colorMode ").append(colorMode); sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes)); sb.append(", hdrCapabilities ").append(hdrCapabilities); + sb.append(", isForceSdr ").append(isForceSdr); sb.append(", allmSupported ").append(allmSupported); sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported); sb.append(", density ").append(densityDpi); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 3c2167e7a4ef..e7fd8f7db182 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -48,6 +48,7 @@ import static android.os.Process.ROOT_UID; import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL; import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH; import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN; +import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.server.display.layout.Layout.Display.POSITION_REAR; @@ -284,7 +285,7 @@ public final class DisplayManagerService extends SystemService { @GuardedBy("mSyncRoot") private int[] mUserDisabledHdrTypes = {}; @Display.HdrCapabilities.HdrType - private int[] mSupportedHdrOutputType; + private int[] mSupportedHdrOutputTypes; @GuardedBy("mSyncRoot") private boolean mAreUserDisabledHdrTypesAllowed = true; @@ -299,10 +300,10 @@ public final class DisplayManagerService extends SystemService { // HDR conversion mode chosen by user @GuardedBy("mSyncRoot") private HdrConversionMode mHdrConversionMode = null; - // Actual HDR conversion mode, which takes app overrides into account. - private HdrConversionMode mOverrideHdrConversionMode = null; + // Whether app has disabled HDR conversion + private boolean mShouldDisableHdrConversion = false; @GuardedBy("mSyncRoot") - private int mSystemPreferredHdrOutputType = Display.HdrCapabilities.HDR_TYPE_INVALID; + private int mSystemPreferredHdrOutputType = HDR_TYPE_INVALID; // The synchronization root for the display manager. @@ -1419,7 +1420,8 @@ public final class DisplayManagerService extends SystemService { } } - private void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) { + @VisibleForTesting + void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) { synchronized (mSyncRoot) { if (userDisabledHdrTypes == null) { Slog.e(TAG, "Null is not an expected argument to " @@ -1437,6 +1439,7 @@ public final class DisplayManagerService extends SystemService { if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) { return; } + String userDisabledFormatsString = ""; if (userDisabledHdrTypes.length != 0) { userDisabledFormatsString = TextUtils.join(",", @@ -1452,6 +1455,15 @@ public final class DisplayManagerService extends SystemService { handleLogicalDisplayChangedLocked(display); }); } + /* Note: it may be expected to reset the Conversion Mode when an HDR type is enabled + and the Conversion Mode is set to System Preferred. This is handled in the Settings + code because in the special case where HDR is indirectly disabled by Force SDR + Conversion, manually enabling HDR is not recognized as an action that reduces the + disabled HDR count. Thus, this case needs to be checked in the Settings code when we + know we're enabling an HDR mode. If we split checking for SystemConversion and + isForceSdr in two places, we may have duplicate calls to resetting to System Conversion + and get two black screens. + */ } } @@ -1464,19 +1476,20 @@ public final class DisplayManagerService extends SystemService { return true; } - private void setAreUserDisabledHdrTypesAllowedInternal( + @VisibleForTesting + void setAreUserDisabledHdrTypesAllowedInternal( boolean areUserDisabledHdrTypesAllowed) { synchronized (mSyncRoot) { if (mAreUserDisabledHdrTypesAllowed == areUserDisabledHdrTypesAllowed) { return; } mAreUserDisabledHdrTypesAllowed = areUserDisabledHdrTypesAllowed; - if (mUserDisabledHdrTypes.length == 0) { - return; - } Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED, areUserDisabledHdrTypesAllowed ? 1 : 0); + if (mUserDisabledHdrTypes.length == 0) { + return; + } int userDisabledHdrTypes[] = {}; if (!mAreUserDisabledHdrTypesAllowed) { userDisabledHdrTypes = mUserDisabledHdrTypes; @@ -1487,6 +1500,14 @@ public final class DisplayManagerService extends SystemService { display.setUserDisabledHdrTypes(finalUserDisabledHdrTypes); handleLogicalDisplayChangedLocked(display); }); + // When HDR conversion mode is set to SYSTEM, modification to + // areUserDisabledHdrTypesAllowed requires refreshing the HDR conversion mode to tell + // the system which HDR types it is not allowed to use. + if (getHdrConversionModeInternal().getConversionMode() + == HdrConversionMode.HDR_CONVERSION_SYSTEM) { + setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + } } } @@ -2357,7 +2378,7 @@ public final class DisplayManagerService extends SystemService { final int preferredHdrOutputType = hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE ? hdrConversionMode.getPreferredHdrOutputType() - : Display.HdrCapabilities.HDR_TYPE_INVALID; + : HDR_TYPE_INVALID; Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType); } @@ -2370,7 +2391,7 @@ public final class DisplayManagerService extends SystemService { ? Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.HDR_FORCE_CONVERSION_TYPE, Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION) - : Display.HdrCapabilities.HDR_TYPE_INVALID; + : HDR_TYPE_INVALID; mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType); setHdrConversionModeInternal(mHdrConversionMode); } @@ -2507,22 +2528,38 @@ public final class DisplayManagerService extends SystemService { }); } + /** + * Returns the HDR output types that are supported by the device's HDR conversion capabilities, + * stripping out any user-disabled HDR types if mAreUserDisabledHdrTypesAllowed is false. + */ @GuardedBy("mSyncRoot") - private int[] getEnabledAutoHdrTypesLocked() { - IntArray autoHdrOutputTypesArray = new IntArray(); + @VisibleForTesting + int[] getEnabledHdrOutputTypesLocked() { + if (mAreUserDisabledHdrTypesAllowed) { + return getSupportedHdrOutputTypesInternal(); + } + // Strip out all HDR formats that are currently user-disabled + IntArray enabledHdrOutputTypesArray = new IntArray(); for (int type : getSupportedHdrOutputTypesInternal()) { - boolean isDisabled = false; + boolean isEnabled = true; for (int disabledType : mUserDisabledHdrTypes) { if (type == disabledType) { - isDisabled = true; + isEnabled = false; break; } } - if (!isDisabled) { - autoHdrOutputTypesArray.add(type); + if (isEnabled) { + enabledHdrOutputTypesArray.add(type); } } - return autoHdrOutputTypesArray.toArray(); + return enabledHdrOutputTypesArray.toArray(); + } + + @VisibleForTesting + int[] getEnabledHdrOutputTypes() { + synchronized (mSyncRoot) { + return getEnabledHdrOutputTypesLocked(); + } } @GuardedBy("mSyncRoot") @@ -2531,7 +2568,7 @@ public final class DisplayManagerService extends SystemService { final int preferredHdrOutputType = mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType(); - if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) { + if (preferredHdrOutputType != HDR_TYPE_INVALID) { int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency(); return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType); } @@ -2565,41 +2602,57 @@ public final class DisplayManagerService extends SystemService { if (!mInjector.getHdrOutputConversionSupport()) { return; } - int[] autoHdrOutputTypes = null; + synchronized (mSyncRoot) { if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM && hdrConversionMode.getPreferredHdrOutputType() - != Display.HdrCapabilities.HDR_TYPE_INVALID) { + != HDR_TYPE_INVALID) { throw new IllegalArgumentException("preferredHdrOutputType must not be set if" + " the conversion mode is HDR_CONVERSION_SYSTEM"); } mHdrConversionMode = hdrConversionMode; storeHdrConversionModeLocked(mHdrConversionMode); - // For auto mode, all supported HDR types are allowed except the ones specifically - // disabled by the user. + // If the HDR conversion is HDR_CONVERSION_SYSTEM, all supported HDR types are allowed + // except the ones specifically disabled by the user. + int[] enabledHdrOutputTypes = null; if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) { - autoHdrOutputTypes = getEnabledAutoHdrTypesLocked(); + enabledHdrOutputTypes = getEnabledHdrOutputTypesLocked(); } int conversionMode = hdrConversionMode.getConversionMode(); int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType(); + // If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then // set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH. - if (mOverrideHdrConversionMode == null) { + if (mShouldDisableHdrConversion) { + conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; + preferredHdrType = -1; + enabledHdrOutputTypes = null; + } else { // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type. - // But, internally SDR is selected by using passthrough mode. + // But, internally SDR is forced by using passthrough mode and not reporting any + // HDR capabilities to apps. if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE - && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) { + && preferredHdrType == HDR_TYPE_INVALID) { conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; + mLogicalDisplayMapper.forEachLocked( + logicalDisplay -> { + if (logicalDisplay.setIsForceSdr(true)) { + handleLogicalDisplayChangedLocked(logicalDisplay); + } + }); + } else { + mLogicalDisplayMapper.forEachLocked( + logicalDisplay -> { + if (logicalDisplay.setIsForceSdr(false)) { + handleLogicalDisplayChangedLocked(logicalDisplay); + } + }); } - } else { - conversionMode = mOverrideHdrConversionMode.getConversionMode(); - preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType(); - autoHdrOutputTypes = null; } mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode( - conversionMode, preferredHdrType, autoHdrOutputTypes); + conversionMode, preferredHdrType, enabledHdrOutputTypes); } } @@ -2621,8 +2674,8 @@ public final class DisplayManagerService extends SystemService { } HdrConversionMode mode; synchronized (mSyncRoot) { - mode = mOverrideHdrConversionMode != null - ? mOverrideHdrConversionMode + mode = mShouldDisableHdrConversion + ? new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH) : mHdrConversionMode; // Handle default: PASSTHROUGH. Don't include the system-preferred type. if (mode == null @@ -2630,8 +2683,6 @@ public final class DisplayManagerService extends SystemService { return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH); } // Handle default or current mode: SYSTEM. Include the system preferred type. - // mOverrideHdrConversionMode and mHdrConversionMode do not include the system - // preferred type, it is kept separately in mSystemPreferredHdrOutputType. if (mode == null || mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) { return new HdrConversionMode( @@ -2642,10 +2693,10 @@ public final class DisplayManagerService extends SystemService { } private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() { - if (mSupportedHdrOutputType == null) { - mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes(); + if (mSupportedHdrOutputTypes == null) { + mSupportedHdrOutputTypes = mInjector.getSupportedHdrOutputTypes(); } - return mSupportedHdrOutputType; + return mSupportedHdrOutputTypes; } void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) { @@ -2831,15 +2882,9 @@ public final class DisplayManagerService extends SystemService { // HDR conversion is disabled in two cases: // - HDR conversion introduces latency and minimal post-processing is requested // - app requests to disable HDR conversion - if (mOverrideHdrConversionMode == null && (disableHdrConversion - || disableHdrConversionForLatency)) { - mOverrideHdrConversionMode = - new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH); - setHdrConversionModeInternal(mHdrConversionMode); - handleLogicalDisplayChangedLocked(display); - } else if (mOverrideHdrConversionMode != null && !disableHdrConversion - && !disableHdrConversionForLatency) { - mOverrideHdrConversionMode = null; + boolean previousShouldDisableHdrConversion = mShouldDisableHdrConversion; + mShouldDisableHdrConversion = disableHdrConversion || disableHdrConversionForLatency; + if (previousShouldDisableHdrConversion != mShouldDisableHdrConversion) { setHdrConversionModeInternal(mHdrConversionMode); handleLogicalDisplayChangedLocked(display); } @@ -3530,9 +3575,9 @@ public final class DisplayManagerService extends SystemService { } int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, - int[] autoHdrTypes) { + int[] allowedHdrOutputTypes) { return DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType, - autoHdrTypes); + allowedHdrOutputTypes); } @Display.HdrCapabilities.HdrType diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index e8be8a449652..007e3a8fde2f 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -518,6 +518,7 @@ final class LogicalDisplay { deviceInfo.supportedColorModes, deviceInfo.supportedColorModes.length); mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities; + mBaseDisplayInfo.isForceSdr = deviceInfo.isForceSdr; mBaseDisplayInfo.userDisabledHdrTypes = mUserDisabledHdrTypes; mBaseDisplayInfo.minimalPostProcessingSupported = deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported; @@ -899,6 +900,29 @@ final class LogicalDisplay { } /** + * Checks whether display is of the type where HDR settings are relevant, and then sets + * whether Force SDR conversion mode is active. isForceSdr is checked by the Display when + * returning HDR capabilities. + * + * @param isForceSdr Whether Force SDR conversion mode is active + * @return Whether Display Manager should call handleLogicalDisplayChangedLocked() + */ + public boolean setIsForceSdr(boolean isForceSdr) { + int displayType = getDisplayInfoLocked().type; + boolean isTargetDisplayType = displayType == Display.TYPE_INTERNAL + || displayType == Display.TYPE_EXTERNAL + || displayType == Display.TYPE_OVERLAY; + + boolean handleLogicalDisplayChangedLocked = false; + if (isTargetDisplayType && mBaseDisplayInfo.isForceSdr != isForceSdr) { + mBaseDisplayInfo.isForceSdr = isForceSdr; + mInfo.set(null); + handleLogicalDisplayChangedLocked = true; + } + return handleLogicalDisplayChangedLocked; + } + + /** * Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay. * * @param targetDisplay The display with which to swap display-devices. diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index ea240c75452d..9d04682f2374 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -196,12 +196,7 @@ final class UpdatableFontDir { File signatureFile = new File(dir, FONT_SIGNATURE_FILE); if (!signatureFile.exists()) { Slog.i(TAG, "The signature file is missing."); - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - return; - } else { - FileUtils.deleteContentsAndDir(dir); - continue; - } + return; } byte[] signature; try { @@ -226,39 +221,33 @@ final class UpdatableFontDir { FontFileInfo fontFileInfo = validateFontFile(fontFile, signature); if (fontConfig == null) { - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - // Use preinstalled font config for checking revision number. - fontConfig = mConfigSupplier.apply(Collections.emptyMap()); - } else { - fontConfig = getSystemFontConfig(); - } + // Use preinstalled font config for checking revision number. + fontConfig = mConfigSupplier.apply(Collections.emptyMap()); } addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */); } - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - // Treat as error if post script name of font family was not installed. - for (int i = 0; i < config.fontFamilies.size(); ++i) { - FontUpdateRequest.Family family = config.fontFamilies.get(i); - for (int j = 0; j < family.getFonts().size(); ++j) { - FontUpdateRequest.Font font = family.getFonts().get(j); - if (mFontFileInfoMap.containsKey(font.getPostScriptName())) { - continue; - } - - if (fontConfig == null) { - fontConfig = mConfigSupplier.apply(Collections.emptyMap()); - } - - if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) { - continue; - } - - Slog.e(TAG, "Unknown font that has PostScript name " - + font.getPostScriptName() + " is requested in FontFamily " - + family.getName()); - return; + // Treat as error if post script name of font family was not installed. + for (int i = 0; i < config.fontFamilies.size(); ++i) { + FontUpdateRequest.Family family = config.fontFamilies.get(i); + for (int j = 0; j < family.getFonts().size(); ++j) { + FontUpdateRequest.Font font = family.getFonts().get(j); + if (mFontFileInfoMap.containsKey(font.getPostScriptName())) { + continue; } + + if (fontConfig == null) { + fontConfig = mConfigSupplier.apply(Collections.emptyMap()); + } + + if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) { + continue; + } + + Slog.e(TAG, "Unknown font that has PostScript name " + + font.getPostScriptName() + " is requested in FontFamily " + + family.getName()); + return; } } @@ -273,9 +262,7 @@ final class UpdatableFontDir { mFontFileInfoMap.clear(); mLastModifiedMillis = 0; FileUtils.deleteContents(mFilesDir); - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - mConfigFile.delete(); - } + mConfigFile.delete(); } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 164b230a0d3b..8e41d18f0953 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1380,7 +1380,8 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private List<Integer> getCecLocalDeviceTypes() { ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices); - if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) + if (!isTvDevice() && isDsmEnabled() + && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) && isArcSupported() && mSoundbarModeFeatureFlagEnabled) { allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); } @@ -1687,7 +1688,11 @@ public class HdmiControlService extends SystemService { private void sendCecCommandWithRetries(HdmiCecMessage command, @Nullable SendMessageCallback callback) { assertRunOnServiceThread(); - HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0); + List<HdmiCecLocalDevice> devices = getAllCecLocalDevices(); + if (devices.isEmpty()) { + return; + } + HdmiCecLocalDevice localDevice = devices.get(0); if (localDevice != null) { sendCecCommandWithoutRetries(command, new SendMessageCallback() { @Override diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index edc35e553244..65adaba62a4d 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -456,6 +456,14 @@ public class InputManagerService extends IInputManager.Stub void registerLocalService(InputManagerInternal localService) { LocalServices.addService(InputManagerInternal.class, localService); } + + KeyboardBacklightControllerInterface getKeyboardBacklightController( + NativeInputManagerService nativeService, PersistentDataStore dataStore) { + return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled() + ? new KeyboardBacklightController(mContext, nativeService, dataStore, + mLooper, mUEventManager) + : new KeyboardBacklightControllerInterface() {}; + } } public InputManagerService(Context context) { @@ -479,10 +487,7 @@ public class InputManagerService extends IInputManager.Stub injector.getLooper(), this) : null; mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(), injector.getUEventManager()); - mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled() - ? new KeyboardBacklightController(mContext, mNative, mDataStore, - injector.getLooper(), injector.getUEventManager()) - : new KeyboardBacklightControllerInterface() {}; + mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore); mStickyModifierStateController = new StickyModifierStateController(); mKeyGestureController = new KeyGestureController(mContext, injector.getLooper()); mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(), @@ -606,6 +611,8 @@ public class InputManagerService extends IInputManager.Stub mKeyRemapper.systemRunning(); mPointerIconCache.systemRunning(); mKeyboardGlyphManager.systemRunning(); + + initKeyGestures(); } private void reloadDeviceAliases() { @@ -2504,6 +2511,59 @@ public class InputManagerService extends IInputManager.Stub null, null, null) == PERMISSION_GRANTED; } + private void initKeyGestures() { + if (!useKeyGestureEventHandler()) { + return; + } + InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); + im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() { + @Override + public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, + @Nullable IBinder focussedToken) { + int deviceId = event.getDeviceId(); + boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE + && !event.isCancelled(); + switch (event.getKeyGestureType()) { + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: + if (complete) { + mKeyboardBacklightController.incrementKeyboardBacklight(deviceId); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: + if (complete) { + mKeyboardBacklightController.decrementKeyboardBacklight(deviceId); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: + // TODO(b/367748270): Add functionality to turn keyboard backlight on/off. + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: + if (complete) { + mNative.toggleCapsLock(deviceId); + } + return true; + default: + return false; + + } + } + + @Override + public boolean isKeyGestureSupported(int gestureType) { + switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: + return true; + default: + return false; + + } + } + }); + } + // Native callback. @SuppressWarnings("unused") private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) { @@ -3207,6 +3267,8 @@ public class InputManagerService extends IInputManager.Stub mKeyboardBacklightController.onInteractiveChanged(interactive); } + // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture + // handler refactoring complete @Override public void toggleCapsLock(int deviceId) { mNative.toggleCapsLock(deviceId); @@ -3294,11 +3356,15 @@ public class InputManagerService extends IInputManager.Stub mKeyboardBacklightController.notifyUserActivity(); } + // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture + // handler refactoring complete @Override public void incrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.incrementKeyboardBacklight(deviceId); } + // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture + // handler refactoring complete @Override public void decrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.decrementKeyboardBacklight(deviceId); diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java index 5ff8568f81b2..0e940d281b09 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java @@ -180,6 +180,10 @@ public class TouchpadDebugView extends LinearLayout { @Override public boolean onTouchEvent(MotionEvent event) { + if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) { + return false; + } + float deltaX; float deltaY; switch (event.getAction()) { diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java index 67c3621b7c8c..2eed9ba95413 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java @@ -27,20 +27,28 @@ import com.android.server.input.TouchpadFingerState; import com.android.server.input.TouchpadHardwareProperties; import com.android.server.input.TouchpadHardwareState; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; + public class TouchpadVisualizationView extends View { private static final String TAG = "TouchpadVizMain"; private static final boolean DEBUG = true; private static final float DEFAULT_RES_X = 47f; private static final float DEFAULT_RES_Y = 45f; + private static final float MAX_TRACE_HISTORY_DURATION_SECONDS = 1f; private final TouchpadHardwareProperties mTouchpadHardwareProperties; private float mScaleFactor; - TouchpadHardwareState mLatestHardwareState = new TouchpadHardwareState(0, 0, 0, 0, - new TouchpadFingerState[]{}); + private final ArrayDeque<TouchpadHardwareState> mHardwareStateHistory = + new ArrayDeque<TouchpadHardwareState>(); + private final Map<Integer, TouchpadFingerState> mTempFingerStatesByTrackingId = new HashMap<>(); private final Paint mOvalStrokePaint; private final Paint mOvalFillPaint; + private final Paint mTracePaint; + private final Paint mCenterPointPaint; private final RectF mTempOvalRect = new RectF(); public TouchpadVisualizationView(Context context, @@ -55,6 +63,29 @@ public class TouchpadVisualizationView extends View { mOvalFillPaint = new Paint(); mOvalFillPaint.setAntiAlias(true); mOvalFillPaint.setARGB(255, 0, 0, 0); + mTracePaint = new Paint(); + mTracePaint.setAntiAlias(false); + mTracePaint.setARGB(255, 0, 0, 255); + mTracePaint.setStyle(Paint.Style.STROKE); + mTracePaint.setStrokeWidth(2); + mCenterPointPaint = new Paint(); + mCenterPointPaint.setAntiAlias(true); + mCenterPointPaint.setARGB(255, 255, 0, 0); + mCenterPointPaint.setStrokeWidth(2); + } + + private void removeOldPoints() { + float latestTimestamp = mHardwareStateHistory.getLast().getTimestamp(); + + while (!mHardwareStateHistory.isEmpty()) { + TouchpadHardwareState oldestPoint = mHardwareStateHistory.getFirst(); + float onScreenTime = latestTimestamp - oldestPoint.getTimestamp(); + if (onScreenTime >= MAX_TRACE_HISTORY_DURATION_SECONDS) { + mHardwareStateHistory.removeFirst(); + } else { + break; + } + } } private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle) { @@ -71,19 +102,22 @@ public class TouchpadVisualizationView extends View { @Override protected void onDraw(Canvas canvas) { + if (mHardwareStateHistory.isEmpty()) { + return; + } + + TouchpadHardwareState latestHardwareState = mHardwareStateHistory.getLast(); + float maximumPressure = 0; - for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) { + for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) { maximumPressure = Math.max(maximumPressure, touchpadFingerState.getPressure()); } - for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) { - float newX = translateRange(mTouchpadHardwareProperties.getLeft(), - mTouchpadHardwareProperties.getRight(), 0, getWidth(), - touchpadFingerState.getPositionX()); + // Visualizing fingers as ovals + for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) { + float newX = translateX(touchpadFingerState.getPositionX()); - float newY = translateRange(mTouchpadHardwareProperties.getTop(), - mTouchpadHardwareProperties.getBottom(), 0, getHeight(), - touchpadFingerState.getPositionY()); + float newY = translateY(touchpadFingerState.getPositionY()); float newAngle = translateRange(0, mTouchpadHardwareProperties.getOrientationMaximum(), 0, 90, touchpadFingerState.getOrientation()); @@ -102,6 +136,28 @@ public class TouchpadVisualizationView extends View { drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle); } + + mTempFingerStatesByTrackingId.clear(); + + // Drawing the trace + for (TouchpadHardwareState currentHardwareState : mHardwareStateHistory) { + for (TouchpadFingerState currentFingerState : currentHardwareState.getFingerStates()) { + TouchpadFingerState prevFingerState = mTempFingerStatesByTrackingId.put( + currentFingerState.getTrackingId(), currentFingerState); + + if (prevFingerState == null) { + continue; + } + + float currentX = translateX(currentFingerState.getPositionX()); + float currentY = translateY(currentFingerState.getPositionY()); + float prevX = translateX(prevFingerState.getPositionX()); + float prevY = translateY(prevFingerState.getPositionY()); + + canvas.drawLine(prevX, prevY, currentX, currentY, mTracePaint); + canvas.drawPoint(currentX, currentY, mCenterPointPaint); + } + } } /** @@ -114,7 +170,18 @@ public class TouchpadVisualizationView extends View { logHardwareState(schs); } - mLatestHardwareState = schs; + if (!mHardwareStateHistory.isEmpty() + && mHardwareStateHistory.getLast().getFingerCount() == 0 + && schs.getFingerCount() > 0) { + mHardwareStateHistory.clear(); + } + + mHardwareStateHistory.addLast(schs); + removeOldPoints(); + + if (DEBUG) { + logFingerTrace(); + } invalidate(); } @@ -128,6 +195,16 @@ public class TouchpadVisualizationView extends View { mScaleFactor = scaleFactor; } + private float translateX(float x) { + return translateRange(mTouchpadHardwareProperties.getLeft(), + mTouchpadHardwareProperties.getRight(), 0, getWidth(), x); + } + + private float translateY(float y) { + return translateRange(mTouchpadHardwareProperties.getTop(), + mTouchpadHardwareProperties.getBottom(), 0, getHeight(), y); + } + private float translateRange(float rangeBeforeMin, float rangeBeforeMax, float rangeAfterMin, float rangeAfterMax, float value) { return rangeAfterMin + (value - rangeBeforeMin) / (rangeBeforeMax - rangeBeforeMin) * ( @@ -154,4 +231,10 @@ public class TouchpadVisualizationView extends View { } } -} + private void logFingerTrace() { + Slog.d(TAG, "Trace size= " + mHardwareStateHistory.size()); + for (TouchpadFingerState tfs : mHardwareStateHistory.getLast().getFingerStates()) { + Slog.d(TAG, "ID= " + tfs.getTrackingId()); + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 4b2c12abe5bb..63bd9ab815b2 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -389,7 +389,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // Reload gnss config for no SIM case mGnssConfiguration.reloadGpsProperties(); } - if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { updateNiSuplMessageListenerRegistration( mGnssConfiguration.isNiSuplMessageInjectionEnabled()); } @@ -538,7 +538,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler); - if (!Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (!Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { updateNiSuplMessageListenerRegistration( mGnssConfiguration.isNiSuplMessageInjectionEnabled()); } @@ -1672,7 +1672,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements if (dumpAll) { mNetworkTimeHelper.dump(pw); pw.println("mSupportsPsds=" + mSupportsPsds); - if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { pw.println("mNiSuplMessageListenerRegistered=" + mNiSuplMessageListenerRegistered); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 158d444bcff2..1e25f1cf1d5e 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -140,7 +140,7 @@ class LockSettingsStorage { try { db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", new String[] {key, Integer.toString(userId)}); - db.insert(TABLE, null, cv); + db.insertOrThrow(TABLE, null, cv); db.setTransactionSuccessful(); mCache.putKeyValue(key, value, userId); } finally { diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index fbe77720b9fc..f54c1f7654dc 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -218,10 +218,16 @@ public final class VibratorHelper { * @param uri {@code Uri} an uri including query parameter "vibraiton_uri" */ public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) { - if (uri == null) { + if (uri == null || uri.isOpaque()) { return null; } - return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri)); + + try { + return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri)); + } catch (Exception e) { + Slog.e(TAG, "Failed to get vibration effect: ", e); + } + return null; } /** Returns if a given vibration can be played by the vibrator that does notification buzz. */ diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java index 41351613331d..5aea356a4173 100644 --- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java +++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.media.AudioAttributes.USAGE_ALARM; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Notification; @@ -42,6 +43,8 @@ import android.util.Log; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import java.util.List; + public class BackgroundUserSoundNotifier { private static final boolean DEBUG = false; @@ -49,11 +52,21 @@ public class BackgroundUserSoundNotifier { private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel"; private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound"; public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER"; - private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID"; - private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID"; private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER"; - /** ID of user with notification displayed, -1 if notification is not showing*/ - private int mUserWithNotification = -1; + private static final String ACTION_DISMISS_NOTIFICATION = + "com.android.server.ACTION_DISMISS_NOTIFICATION"; + /** + * The clientUid from the AudioFocusInfo of the background user, + * for which an active notification is currently displayed. + * Set to -1 if no notification is being shown. + * TODO: b/367615180 - add support for multiple simultaneous alarms + */ + @VisibleForTesting + int mNotificationClientUid = -1; + @VisibleForTesting + AudioPolicy mFocusControlAudioPolicy; + @VisibleForTesting + BackgroundUserListener mBgUserListener; private final Context mSystemUserContext; @VisibleForTesting final NotificationManager mNotificationManager; @@ -67,11 +80,18 @@ public class BackgroundUserSoundNotifier { mSystemUserContext = context; mNotificationManager = mSystemUserContext.getSystemService(NotificationManager.class); mUserManager = mSystemUserContext.getSystemService(UserManager.class); + createNotificationChannel(); + setupFocusControlAudioPolicy(); + } + + /** + * Creates a dedicated channel for background user related notifications. + */ + private void createNotificationChannel() { NotificationChannel channel = new NotificationChannel(BUSN_CHANNEL_ID, BUSN_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH); channel.setSound(null, null); mNotificationManager.createNotificationChannel(channel); - setupFocusControlAudioPolicy(); } private void setupFocusControlAudioPolicy() { @@ -81,15 +101,16 @@ public class BackgroundUserSoundNotifier { ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class); registerReceiver(am); - BackgroundUserListener bgUserListener = new BackgroundUserListener(mSystemUserContext); + mBgUserListener = new BackgroundUserListener(mSystemUserContext); AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext); focusControlPolicyBuilder.setLooper(Looper.getMainLooper()); - focusControlPolicyBuilder.setAudioPolicyFocusListener(bgUserListener); + focusControlPolicyBuilder.setAudioPolicyFocusListener(mBgUserListener); - AudioPolicy mFocusControlAudioPolicy = focusControlPolicyBuilder.build(); + mFocusControlAudioPolicy = focusControlPolicyBuilder.build(); int status = mSystemUserContext.getSystemService(AudioManager.class) .registerAudioPolicy(mFocusControlAudioPolicy); + if (status != AudioManager.SUCCESS) { Log.w(LOG_TAG , "Could not register the service's focus" + " control audio policy, error: " + status); @@ -117,123 +138,170 @@ public class BackgroundUserSoundNotifier { @SuppressLint("MissingPermission") public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { - BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi); + BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(); } } + @VisibleForTesting + BackgroundUserListener getAudioPolicyFocusListener() { + return mBgUserListener; + } + /** * Registers a BroadcastReceiver for actions related to background user sound notifications. * When ACTION_MUTE_SOUND is received, it mutes a background user's alarm sound. * When ACTION_SWITCH_USER is received, a switch to the background user with alarm is started. */ - private void registerReceiver(ActivityManager service) { + private void registerReceiver(ActivityManager activityManager) { BroadcastReceiver backgroundUserNotificationBroadcastReceiver = new BroadcastReceiver() { @SuppressLint("MissingPermission") @Override public void onReceive(Context context, Intent intent) { - if (!(intent.hasExtra(EXTRA_NOTIFICATION_ID) - && intent.hasExtra(EXTRA_CURRENT_USER_ID) - && intent.hasExtra(Intent.EXTRA_USER_ID))) { + if (mNotificationClientUid == -1) { return; } - final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); + dismissNotification(); if (DEBUG) { - Log.d(LOG_TAG, - "User with alarm id " + intent.getIntExtra(Intent.EXTRA_USER_ID, - -1) + " current user id " + intent.getIntExtra( - EXTRA_CURRENT_USER_ID, -1)); + final int actionIndex = intent.getAction().lastIndexOf(".") + 1; + final String action = intent.getAction().substring(actionIndex); + Log.d(LOG_TAG, "Action requested: " + action + ", by userId " + + ActivityManager.getCurrentUser() + " for alarm on user " + + UserHandle.getUserHandleForUid(mNotificationClientUid)); } - mUserWithNotification = -1; - mNotificationManager.cancelAsUser(LOG_TAG, notificationId, - UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1))); + if (ACTION_MUTE_SOUND.equals(intent.getAction())) { - final AudioManager audioManager = - mSystemUserContext.getSystemService(AudioManager.class); - if (audioManager != null) { - for (AudioPlaybackConfiguration apc : - audioManager.getActivePlaybackConfigurations()) { - if (apc.getAudioAttributes().getUsage() == USAGE_ALARM) { - if (apc.getPlayerProxy() != null) { - apc.getPlayerProxy().stop(); - } - } - } - } + muteAlarmSounds(mSystemUserContext); } else if (ACTION_SWITCH_USER.equals(intent.getAction())) { - service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1)); + activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid)); } + + mNotificationClientUid = -1; } }; IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_MUTE_SOUND); filter.addAction(ACTION_SWITCH_USER); + filter.addAction(ACTION_DISMISS_NOTIFICATION); mSystemUserContext.registerReceiver(backgroundUserNotificationBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); } /** + * Stop player proxy for the ongoing alarm and drop focus for its AudioFocusInfo. + */ + @VisibleForTesting + void muteAlarmSounds(Context context) { + AudioManager audioManager = context.getSystemService(AudioManager.class); + if (audioManager != null) { + for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) { + if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) { + apc.getPlayerProxy().stop(); + } + } + } + } + + /** * Check if sound is coming from background user and show notification is required. */ @VisibleForTesting - void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context - foregroundContext) throws RemoteException { + void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext) + throws RemoteException { final int userId = UserHandle.getUserId(afi.getClientUid()); final int usage = afi.getAttributes().getUsage(); UserInfo userInfo = mUserManager.getUserInfo(userId); - if (userInfo != null && userId != foregroundContext.getUserId()) { + // Only show notification if the sound is coming from background user and the notification + // is not already shown. + if (userInfo != null && userId != foregroundContext.getUserId() + && mNotificationClientUid == -1) { //TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE if (usage == USAGE_ALARM) { - Intent muteIntent = createIntent(ACTION_MUTE_SOUND, afi, foregroundContext, userId); - PendingIntent mutePI = PendingIntent.getBroadcast(mSystemUserContext, 0, - muteIntent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_IMMUTABLE); - Intent switchIntent = createIntent(ACTION_SWITCH_USER, afi, foregroundContext, - userId); - PendingIntent switchPI = PendingIntent.getBroadcast(mSystemUserContext, 0, - switchIntent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_IMMUTABLE); - - mUserWithNotification = foregroundContext.getUserId(); - mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(), - createNotification(userInfo.name, mutePI, switchPI, foregroundContext), + if (DEBUG) { + Log.d(LOG_TAG, "Alarm ringing on background user " + userId + + ", displaying notification for current user " + + foregroundContext.getUserId()); + } + + mNotificationClientUid = afi.getClientUid(); + + mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid, + createNotification(userInfo.name, foregroundContext), foregroundContext.getUser()); } } } /** - * If notification is present, dismisses it. To be called when the relevant sound loses focus. + * Dismisses notification if the associated focus has been removed from the focus stack. + * Notification remains if the focus is temporarily lost due to another client taking over the + * focus ownership. */ - private void dismissNotificationIfNecessary(AudioFocusInfo afi) { - if (mUserWithNotification >= 0) { - mNotificationManager.cancelAsUser(LOG_TAG, afi.getClientUid(), - UserHandle.of(mUserWithNotification)); + @VisibleForTesting + void dismissNotificationIfNecessary() { + if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) { + if (DEBUG) { + Log.d(LOG_TAG, "Alarm ringing on background user " + + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier() + + " left focus stack, dismissing notification"); + } + dismissNotification(); + mNotificationClientUid = -1; } - mUserWithNotification = -1; } - private Intent createIntent(String intentAction, AudioFocusInfo afi, Context fgUserContext, - int userId) { + /** + * Dismisses notification for all users in case user switch occurred after notification was + * shown. + */ + @SuppressLint("MissingPermission") + private void dismissNotification() { + mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL); + } + + /** + * Returns AudioFocusInfo associated with the current notification. + */ + @SuppressLint("MissingPermission") + @VisibleForTesting + @Nullable + AudioFocusInfo getAudioFocusInfoForNotification() { + if (mNotificationClientUid >= 0) { + List<AudioFocusInfo> stack = mFocusControlAudioPolicy.getFocusStack(); + for (int i = stack.size() - 1; i >= 0; i--) { + if (stack.get(i).getClientUid() == mNotificationClientUid) { + return stack.get(i); + } + } + } + return null; + } + + private PendingIntent createPendingIntent(String intentAction) { final Intent intent = new Intent(intentAction); - intent.putExtra(EXTRA_CURRENT_USER_ID, fgUserContext.getUserId()); - intent.putExtra(EXTRA_NOTIFICATION_ID, afi.getClientUid()); - intent.putExtra(Intent.EXTRA_USER_ID, userId); - return intent; + PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + return resultPI; } - private Notification createNotification(String userName, PendingIntent muteIntent, - PendingIntent switchIntent, Context fgContext) { + @VisibleForTesting + Notification createNotification(String userName, Context fgContext) { final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm, userName); final int icon = R.drawable.ic_audio_alarm; + + PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND); + PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER); + PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION); + final Notification.Action mute = new Notification.Action.Builder(null, fgContext.getString(R.string.bg_user_sound_notification_button_mute), - muteIntent).build(); + mutePI).build(); final Notification.Action switchUser = new Notification.Action.Builder(null, fgContext.getString(R.string.bg_user_sound_notification_button_switch_user), - switchIntent).build(); + switchPI).build(); + Notification.Builder notificationBuilder = new Notification.Builder(mSystemUserContext, BUSN_CHANNEL_ID) .setSmallIcon(icon) @@ -243,16 +311,18 @@ public class BackgroundUserSoundNotifier { .setOngoing(true) .setColor(fgContext.getColor(R.color.system_notification_accent_color)) .setContentTitle(title) - .setContentIntent(muteIntent) + .setContentIntent(mutePI) .setAutoCancel(true) + .setDeleteIntent(dismissNotificationPI) .setVisibility(Notification.VISIBILITY_PUBLIC); + if (mUserManager.isUserSwitcherEnabled() && (mUserManager.getUserSwitchability( - UserHandle.of(fgContext.getUserId())) == UserManager.SWITCHABILITY_STATUS_OK)) { + fgContext.getUser()) == UserManager.SWITCHABILITY_STATUS_OK)) { notificationBuilder.setActions(mute, switchUser); } else { notificationBuilder.setActions(mute); } + return notificationBuilder.build(); } } - diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index ee15bec0d62b..efd58ed6edcc 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -92,7 +92,6 @@ import android.graphics.Rect; import android.multiuser.Flags; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IInterface; @@ -215,7 +214,7 @@ public class LauncherAppsService extends SystemService { @VisibleForTesting static class LauncherAppsImpl extends ILauncherApps.Stub { - private static final boolean DEBUG = Build.IS_DEBUGGABLE; + private static final boolean DEBUG = false; private static final String TAG = "LauncherAppsService"; private static final String NAMESPACE_MULTIUSER = "multiuser"; private static final String FLAG_NON_SYSTEM_ACCESS_TO_HIDDEN_PROFILES = @@ -496,28 +495,8 @@ public class LauncherAppsService extends SystemService { private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid, int targetUserId, String message) { - if (DEBUG) { - final AndroidPackage callingPackage = - mPackageManagerInternal.getPackage(callingUid); - final String callingPackageName = callingPackage == null - ? null : callingPackage.getPackageName(); - Slog.v(TAG, "canAccessProfile called by " + callingPackageName - + " for user " + callingUserId - + " requesting to access user " - + targetUserId + " when invoking " + message); - } - if (targetUserId == callingUserId) { - if (DEBUG) { - Slog.v(TAG, message + " passed canAccessProfile for targetuser" - + targetUserId + " because it is the same as the calling user"); - } - return true; - } + if (targetUserId == callingUserId) return true; if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) { - if (DEBUG) { - Slog.v(TAG, message + " passed because calling process" - + "has permission to interact across users"); - } return true; } @@ -535,25 +514,11 @@ public class LauncherAppsService extends SystemService { if (isHiddenProfile(UserHandle.of(targetUserId)) && !canAccessHiddenProfile(callingUid, callingPid)) { - Slog.w(TAG, message + " for hidden profile user " + targetUserId - + " from " + callingUserId + " not allowed"); - return false; } - final boolean ret = mUserManagerInternal.isProfileAccessible( - callingUserId, targetUserId, message, true); - if (DEBUG) { - final AndroidPackage callingPackage = - mPackageManagerInternal.getPackage(callingUid); - final String callingPackageName = callingPackage == null - ? null : callingPackage.getPackageName(); - Slog.v(TAG, "canAccessProfile returned " + ret + " for " + callingPackageName - + " for user " + callingUserId - + " requesting to access user " - + targetUserId + " when invoking " + message); - } - return ret; + return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId, + message, true); } private boolean isHiddenProfile(UserHandle targetUser) { @@ -1376,10 +1341,6 @@ public class LauncherAppsService extends SystemService { @Override public void pinShortcuts(String callingPackage, String packageName, List<String> ids, UserHandle targetUser) { - if (DEBUG) { - Slog.v(TAG, "pinShortcuts: " + callingPackage + " is pinning shortcuts from " - + packageName + " for user " + targetUser); - } if (!mShortcutServiceInternal .areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) { // Requires strict ACCESS_SHORTCUTS permission for user-profiles with items @@ -1390,11 +1351,6 @@ public class LauncherAppsService extends SystemService { } ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) { - if (DEBUG) { - Slog.v(TAG, "pinShortcuts: " + callingPackage - + " is pinning shortcuts from " + packageName - + " for user " + targetUser + " but cannot access profile"); - } return; } @@ -2451,7 +2407,7 @@ public class LauncherAppsService extends SystemService { final int callbackUserId = callbackUser.getIdentifier(); final int shortcutUserId = shortcutUser.getIdentifier(); - if (shortcutUser == callbackUser) return true; + if ((shortcutUser.equals(callbackUser))) return true; return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId, null, false); } @@ -2485,16 +2441,28 @@ public class LauncherAppsService extends SystemService { final BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(cookie, user, "onPackageRemoved")) { + // b/350144057 + Slog.d(TAG, "onPackageRemoved: Skipping - profile not enabled" + + " or not accessible for user=" + user + + ", packageName=" + packageName); continue; } if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId( cookie.callingUid))) { + // b/350144057 + Slog.d(TAG, "onPackageRemoved: Skipping - appId not allowed" + + " for user=" + user + + ", packageName=" + packageName); continue; } try { + // b/350144057 + Slog.d(TAG, "onPackageRemoved: triggering onPackageRemoved" + + " for user=" + user + + ", packageName=" + packageName); listener.onPackageRemoved(user, packageName); } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + Slog.d(TAG, "onPackageRemoved: Callback failed ", re); } } } finally { @@ -2524,15 +2492,27 @@ public class LauncherAppsService extends SystemService { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(cookie, user, "onPackageAdded")) { + // b/350144057 + Slog.d(TAG, "onPackageAdded: Skipping - profile not enabled" + + " or not accessible for user=" + user + + ", packageName=" + packageName); continue; } if (!isPackageVisibleToListener(packageName, cookie, user)) { + // b/350144057 + Slog.d(TAG, "onPackageAdded: Skipping - package filtered" + + " for user=" + user + + ", packageName=" + packageName); continue; } try { + // b/350144057 + Slog.d(TAG, "onPackageAdded: triggering onPackageAdded" + + " for user=" + user + + ", packageName=" + packageName); listener.onPackageAdded(user, packageName); } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + Slog.d(TAG, "onPackageAdded: Callback failed ", re); } } } finally { @@ -2566,7 +2546,7 @@ public class LauncherAppsService extends SystemService { try { listener.onPackageChanged(user, packageName); } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + Slog.d(TAG, "onPackageChanged: Callback failed ", re); } } } finally { diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index d65e30be9edb..045d4db0a1f1 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -42,7 +42,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** * Launcher information used by {@link ShortcutService}. @@ -129,15 +128,9 @@ class ShortcutLauncher extends ShortcutPackageItem { */ public void pinShortcuts(@UserIdInt int packageUserId, @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) { - if (ShortcutService.DEBUG) { - Slog.v(TAG, "ShortcutLauncher#pinShortcuts: pin shortcuts from " + packageName - + " with userId=" + packageUserId + " shortcutIds=" - + ids.stream().collect(Collectors.joining(", ", "[", "]"))); - } final ShortcutPackage packageShortcuts = mShortcutUser.getPackageShortcutsIfExists(packageName); if (packageShortcuts == null) { - Slog.w(TAG, "ShortcutLauncher#pinShortcuts packageShortcuts is null"); return; // No need to instantiate. } @@ -162,10 +155,6 @@ class ShortcutLauncher extends ShortcutPackageItem { final String id = ids.get(i); final ShortcutInfo si = packageShortcuts.findShortcutById(id); if (si == null) { - if (ShortcutService.DEBUG) { - Slog.w(TAG, "ShortcutLauncher#pinShortcuts: cannot pin " - + id + " because it does not exist"); - } continue; } if (si.isDynamic() || si.isLongLived() @@ -185,13 +174,6 @@ class ShortcutLauncher extends ShortcutPackageItem { } } } - if (ShortcutService.DEBUG) { - Slog.v(TAG, "ShortcutLauncher#pinShortcuts: " - + " newSet: " + newSet.stream().collect( - Collectors.joining(", ", "[", "]")) - + " floatingSet: " + floatingSet.stream().collect( - Collectors.joining(", ", "[", "]"))); - } mPinnedShortcuts.put(up, newSet); } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index c9ad4988f8ca..60056eb471d1 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -729,11 +729,6 @@ class ShortcutPackage extends ShortcutPackageItem { } pinnedShortcuts.addAll(pinned); }); - if (ShortcutService.DEBUG) { - Slog.v(TAG, "ShortcutPackage#refreshPinnedFlags: " - + " pinnedShortcuts: " + pinnedShortcuts.stream().collect( - Collectors.joining(", ", "[", "]"))); - } // Secondly, update the pinned state if necessary. final List<ShortcutInfo> pinned = findAll(pinnedShortcuts); if (pinned != null) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index ea495c9bee9c..a3ff1952205f 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -169,7 +169,7 @@ import java.util.stream.Collectors; public class ShortcutService extends IShortcutService.Stub { static final String TAG = "ShortcutService"; - static final boolean DEBUG = Build.IS_DEBUGGABLE; // STOPSHIP if true + static final boolean DEBUG = false; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE; @@ -3206,11 +3206,6 @@ public class ShortcutService extends IShortcutService.Stub { public void pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId) { - if (DEBUG) { - Slog.v(TAG, "pinShortcuts: " + callingPackage + ", with userId=" + launcherUserId - + ", is trying to pin shortcuts from " + packageName - + " with userId=" + userId); - } // Calling permission must be checked by LauncherAppsImpl. Preconditions.checkStringNotEmpty(packageName, "packageName"); Objects.requireNonNull(shortcutIds, "shortcutIds"); @@ -3235,11 +3230,6 @@ public class ShortcutService extends IShortcutService.Stub { && !si.isDeclaredInManifest(), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO, callingPackage, launcherUserId, false); - } else { - if (DEBUG) { - Slog.w(TAG, "specified package " + packageName + ", with userId=" + userId - + ", doesn't exist."); - } } // Get list of shortcuts that will get unpinned. ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId); @@ -5458,17 +5448,6 @@ public class ShortcutService extends IShortcutService.Stub { */ private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds, ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { - if (DEBUG) { - Slog.v(TAG, "prepareChangedShortcuts: " - + " changedIds=" + (changedIds == null - ? "n/a" : changedIds.stream().collect(Collectors.joining(", ", "[", "]"))) - + " newIds=" + (newIds == null - ? "n/a" : newIds.stream().collect(Collectors.joining(", ", "[", "]"))) - + " deletedList=" + (deletedList == null - ? "n/a" : deletedList.stream().map(ShortcutInfo::getId).collect( - Collectors.joining(", ", "[", "]"))) - + " ps=" + (ps == null ? "n/a" : ps.getPackageName())); - } if (ps == null) { // This can happen when package restore is not finished yet. return null; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a683a8c54849..89417f3765ff 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1090,6 +1090,21 @@ public class UserManagerService extends IUserManager.Stub { mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null; mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler); emulateSystemUserModeIfNeeded(); + initPropertyInvalidatedCaches(); + } + + /** + * This method is used to invalidate the caches at server statup, + * so that caches can start working. + */ + private static final void initPropertyInvalidatedCaches() { + if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) { + UserManager.invalidateIsUserUnlockedCache(); + UserManager.invalidateQuietModeEnabledCache(); + UserManager.invalidateStaticUserProperties(); + UserManager.invalidateUserPropertiesCache(); + UserManager.invalidateUserSerialNumberCache(); + } } private boolean doesDeviceHardwareSupportPrivateSpace() { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ef37464704ae..02c02b04c5de 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3849,10 +3849,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS: case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH: case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH: - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: return true; default: return false; @@ -4010,25 +4006,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { sendSwitchKeyboardLayout(displayId, focusedToken, direction); } return true; - // TODO (b/358569822): Move these input specific gesture handling to IMS since we - // are calling into InputManager through internal API anyways - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: - if (complete) { - mInputManagerInternal.incrementKeyboardBacklight(deviceId); - } - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: - if (complete) { - mInputManagerInternal.decrementKeyboardBacklight(deviceId); - } - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: - if (complete) { - mInputManagerInternal.toggleCapsLock(deviceId); - } - return true; } return false; } diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 96a25dac21e3..1e82b8999834 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -322,9 +322,16 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { if (SubscriptionManager.isValidSubscriptionId(subId)) { // Get only configs as needed to save memory. - final PersistableBundle carrierConfig = - CarrierConfigManager.getCarrierConfigSubset(mContext, subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + PersistableBundle carrierConfig = new PersistableBundle(); + try { + carrierConfig = + mCarrierConfigManager.getConfigForSubId( + subId, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + + } catch (RuntimeException exception) { + Slog.w(TAG, "CarrierConfigLoader is not available."); + } + if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) { mReadySubIdsBySlotId.put(slotId, subId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java index a698429ff09e..15f86e9c08ff 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperData.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java @@ -78,7 +78,7 @@ class WallpaperData { /** * The component name of the currently set live wallpaper. */ - ComponentName wallpaperComponent; + private ComponentName mWallpaperComponent; // TODO(b/347235611) Remove this field /** @@ -195,7 +195,7 @@ class WallpaperData { */ WallpaperData(WallpaperData source) { this.userId = source.userId; - this.wallpaperComponent = source.wallpaperComponent; + this.mWallpaperComponent = source.mWallpaperComponent; this.mWhich = source.mWhich; this.wallpaperId = source.wallpaperId; this.cropHint.set(source.cropHint); @@ -230,6 +230,14 @@ class WallpaperData { return result; } + ComponentName getComponent() { + return mWallpaperComponent; + } + + void setComponent(ComponentName componentName) { + this.mWallpaperComponent = componentName; + } + @Override public String toString() { StringBuilder out = new StringBuilder(defaultString(this)); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index b15facb2945c..e3e83b3e1fd7 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -189,13 +189,13 @@ public class WallpaperDataParser { String comp = parser.getAttributeValue(null, "component"); if (removeNextWallpaperComponent()) { - wallpaperToParse.wallpaperComponent = comp != null + wallpaperToParse.setComponent(comp != null ? ComponentName.unflattenFromString(comp) - : null; - if (wallpaperToParse.wallpaperComponent == null - || "android".equals(wallpaperToParse.wallpaperComponent + : null); + if (wallpaperToParse.getComponent() == null + || "android".equals(wallpaperToParse.getComponent() .getPackageName())) { - wallpaperToParse.wallpaperComponent = mImageWallpaper; + wallpaperToParse.setComponent(mImageWallpaper); } } else { wallpaperToParse.nextWallpaperComponent = comp != null @@ -219,7 +219,7 @@ public class WallpaperDataParser { Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); Slog.v(TAG, "mName:" + wallpaper.name); if (removeNextWallpaperComponent()) { - Slog.v(TAG, "mWallpaperComponent:" + wallpaper.wallpaperComponent); + Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent()); } else { Slog.v(TAG, "mNextWallpaperComponent:" + wallpaper.nextWallpaperComponent); @@ -340,7 +340,7 @@ public class WallpaperDataParser { getAttributeInt(parser, "totalCropTop", 0), getAttributeInt(parser, "totalCropRight", 0), getAttributeInt(parser, "totalCropBottom", 0)); - ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; if (multiCrop() && mImageWallpaper.equals(componentName)) { wallpaper.mCropHints = new SparseArray<>(); @@ -480,7 +480,7 @@ public class WallpaperDataParser { out.startTag(null, tag); out.attributeInt(null, "id", wallpaper.wallpaperId); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { if (wallpaper.mCropHints == null) { Slog.e(TAG, "cropHints should not be null when saved"); wallpaper.mCropHints = new SparseArray<>(); @@ -580,10 +580,10 @@ public class WallpaperDataParser { } out.attribute(null, "name", wallpaper.name); - if (wallpaper.wallpaperComponent != null - && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) { + if (wallpaper.getComponent() != null + && !wallpaper.getComponent().equals(mImageWallpaper)) { out.attribute(null, "component", - wallpaper.wallpaperComponent.flattenToShortString()); + wallpaper.getComponent().flattenToShortString()); } if (wallpaper.allowBackup) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 6cc37ddda9bc..4754ffb5cf6e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -276,7 +276,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final boolean isMigration = moved && lockWallpaperChanged; final boolean isRestore = moved && !isMigration; final boolean isAppliedToLock = (wallpaper.mWhich & FLAG_LOCK) != 0; - final boolean needsUpdate = wallpaper.wallpaperComponent == null + final boolean needsUpdate = wallpaper.getComponent() == null || event != CLOSE_WRITE // includes the MOVED_TO case || wallpaper.imageWallpaperPending; @@ -527,7 +527,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); + if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.getComponent()); String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; @@ -550,8 +550,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Not having a wallpaperComponent means it's a lock screen wallpaper. - final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) - || wallpaper.wallpaperComponent == null; + final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.getComponent()) + || wallpaper.getComponent() == null; if (imageWallpaper && wallpaper.getCropFile().exists()) { cropFile = wallpaper.getCropFile().getAbsolutePath(); } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) { @@ -824,13 +824,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); - t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent); + t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent()); if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken); mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId, null /* options */); mWindowManagerInternal.setWallpaperShowWhenLocked( mToken, (wallpaper.mWhich & FLAG_LOCK) != 0); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { mWindowManagerInternal.setWallpaperCropHints(mToken, mWallpaperCropper.getRelativeCropHints(wallpaper)); } else { @@ -906,7 +906,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) { - Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.getComponent() + ", reverting to built-in wallpaper!"); clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null); } @@ -1035,9 +1035,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { Slog.w(TAG, "Wallpaper service gone: " + name); - if (!Objects.equals(name, mWallpaper.wallpaperComponent)) { + if (!Objects.equals(name, mWallpaper.getComponent())) { Slog.e(TAG, "Does not match expected wallpaper component " - + mWallpaper.wallpaperComponent); + + mWallpaper.getComponent()); } mService = null; forEachDisplayConnector(connector -> connector.mEngine = null); @@ -1065,7 +1065,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS); if (DEBUG_LIVE) { Slog.i(TAG, - "Started wallpaper reconnect timeout for " + mWallpaper.wallpaperComponent); + "Started wallpaper reconnect timeout for " + mWallpaper.getComponent()); } } @@ -1081,7 +1081,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); // The broadcast of package update could be delayed after service disconnected. Try // to re-bind the service for 10 seconds. mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND; @@ -1110,7 +1110,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // The wallpaper disappeared. If this isn't a system-default one, track // crashes and fall back to default if it continues to misbehave. if (this == mWallpaper.connection) { - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId && !Objects.equals(mDefaultWallpaperComponent, wpService) @@ -1188,7 +1188,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Do not broadcast changes on ImageWallpaper since it's handled // internally by this class. - boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent); + boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.getComponent()); if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) { return; } @@ -1303,7 +1303,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mNewWallpaper.mWhich == FLAG_SYSTEM) { // New wp is system only, so old system+lock is now lock only final boolean originalIsStatic = mImageWallpaper.equals( - mOriginalSystem.wallpaperComponent); + mOriginalSystem.getComponent()); if (originalIsStatic) { // Static wp: image file rename has already been tried via // migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map @@ -1314,8 +1314,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.v(TAG, "static system+lock to system success"); } - lockWp.wallpaperComponent = - mOriginalSystem.wallpaperComponent; + lockWp.setComponent(mOriginalSystem.getComponent()); lockWp.connection = mOriginalSystem.connection; lockWp.connection.mWallpaper = lockWp; mOriginalSystem.mWhich = FLAG_LOCK; @@ -1376,7 +1375,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - final ComponentName wpService = wallpaper.wallpaperComponent; + final ComponentName wpService = wallpaper.getComponent(); if (wpService != null && wpService.getPackageName().equals(packageName)) { if (DEBUG_LIVE) { Slog.i(TAG, "Wallpaper " + wpService + " update has finished"); @@ -1402,8 +1401,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { doPackagesChangedLocked(true, wallpaper); } } @@ -1417,10 +1416,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { if (DEBUG_LIVE) { - Slog.i(TAG, "Wallpaper service " + wallpaper.wallpaperComponent + Slog.i(TAG, "Wallpaper service " + wallpaper.getComponent() + " is updating"); } wallpaper.wallpaperUpdating = true; @@ -1462,15 +1461,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) { boolean changed = false; - if (wallpaper.wallpaperComponent != null) { - int change = isPackageDisappearing(wallpaper.wallpaperComponent + if (wallpaper.getComponent() != null) { + int change = isPackageDisappearing(wallpaper.getComponent() .getPackageName()); if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { changed = true; if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } @@ -1485,15 +1484,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } } - if (wallpaper.wallpaperComponent != null - && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) { + if (wallpaper.getComponent() != null + && isPackageModified(wallpaper.getComponent().getPackageName())) { try { - mContext.getPackageManager().getServiceInfo(wallpaper.wallpaperComponent, + mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(), PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } @@ -1636,8 +1635,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // sure we have something to render boolean isImageComponent; if (removeNextWallpaperComponent()) { - isImageComponent = wallpaper.wallpaperComponent == null - || mImageWallpaper.equals(wallpaper.wallpaperComponent); + isImageComponent = wallpaper.getComponent() == null + || mImageWallpaper.equals(wallpaper.getComponent()); } else { isImageComponent = mImageWallpaper.equals(wallpaper.nextWallpaperComponent); } @@ -1892,10 +1891,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final ComponentName cname; if (removeNextWallpaperComponent()) { - cname = wallpaper.wallpaperComponent; + cname = wallpaper.getComponent(); } else { - cname = (wallpaper.wallpaperComponent != null) - ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; + cname = (wallpaper.getComponent() != null) + ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; } if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) { // We failed to bind the desired wallpaper, but that might @@ -1927,7 +1926,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // We might end up persisting the current wallpaper data // while locked, so pretend like the component was actually // bound into place - wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; + wallpaper.setComponent(wallpaper.nextWallpaperComponent); } final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich); @@ -2004,7 +2003,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // lock only case: set the system wallpaper component to both screens if (which == FLAG_LOCK) { - component = wallpaper.wallpaperComponent; + component = wallpaper.getComponent(); finalWhich = FLAG_LOCK | FLAG_SYSTEM; } else { component = null; @@ -2310,7 +2309,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub checkPermission(READ_WALLPAPER_INTERNAL); WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId) : mWallpaperMap.get(userId); - if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) { return null; } SparseArray<Rect> relativeSuggestedCrops = @@ -2760,7 +2759,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap) .get(mCurrentUserId); if (wallpaperData == null) return false; - return mImageWallpaper.equals(wallpaperData.wallpaperComponent); + return mImageWallpaper.equals(wallpaperData.getComponent()); } } @@ -2996,7 +2995,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId); final boolean systemIsStatic = originalSystemWallpaper != null && mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; /* If we're setting system but not lock, and lock is currently sharing the system @@ -3190,7 +3189,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } final boolean systemIsStatic = mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; if (which == FLAG_SYSTEM && systemIsBoth && systemIsStatic) { @@ -3212,7 +3211,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub liveSync = new WallpaperDestinationChangeHandler( newWallpaper); boolean same = changingToSame(name, newWallpaper.connection, - newWallpaper.wallpaperComponent); + newWallpaper.getComponent()); /* * If we have a shared system+lock wallpaper, and we reapply the same wallpaper @@ -3243,7 +3242,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } boolean lockBitmapCleared = false; - if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) { + if (!mImageWallpaper.equals(newWallpaper.getComponent())) { clearWallpaperBitmaps(newWallpaper); lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK; } @@ -3324,7 +3323,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Has the component changed? if (!force && changingToSame(componentName, wallpaper.connection, - wallpaper.wallpaperComponent)) { + wallpaper.getComponent())) { try { if (DEBUG_LIVE) { Slog.v(TAG, "Changing to the same component, ignoring"); @@ -3461,7 +3460,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } maybeDetachLastWallpapers(wallpaper); - wallpaper.wallpaperComponent = componentName; + wallpaper.setComponent(componentName); wallpaper.connection = newConn; newConn.mReply = reply; updateCurrentWallpapers(wallpaper); @@ -3586,7 +3585,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private void clearWallpaperComponentLocked(WallpaperData wallpaper) { - wallpaper.wallpaperComponent = null; + wallpaper.setComponent(null); detachWallpaperLocked(wallpaper); } @@ -3831,7 +3830,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.wallpaperId = makeWallpaperIdLocked(); // always bump id at restore wallpaper.allowBackup = true; // by definition if it was restored ComponentName componentName = - removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + removeNextWallpaperComponent() ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; if (componentName != null && !componentName.equals(mImageWallpaper)) { wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS; @@ -3907,7 +3906,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (multiCrop()) pw.print(" mCropHints="); pw.println(wallpaper.mCropHints); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); - pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); + pw.print(" mWallpaperComponent="); pw.println(wallpaper.getComponent()); pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim); pw.println(" mUidToDimAmount:"); diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java index 3be266e2951b..f069dcdbc86b 100644 --- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java @@ -145,11 +145,13 @@ class AppCompatSizeCompatModePolicy { } } - void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) { + void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds, + @NonNull Configuration newParentConfig) { mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy() .findOpaqueNotFinishingActivityBelow() .map(activityRecord -> mSizeCompatScale) - .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds)); + .orElseGet(() -> calculateSizeCompatScale( + resolvedAppBounds, containerAppBounds, newParentConfig)); } void clearSizeCompatModeAttributes() { @@ -290,7 +292,7 @@ class AppCompatSizeCompatModePolicy { // Calculates the scale the size compatibility bounds into the region which is available // to application. final float lastSizeCompatScale = mSizeCompatScale; - updateSizeCompatScale(resolvedAppBounds, containerAppBounds); + updateSizeCompatScale(resolvedAppBounds, containerAppBounds, newParentConfiguration); final int containerTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = @@ -423,7 +425,7 @@ class AppCompatSizeCompatModePolicy { } private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds, - @NonNull Rect containerAppBounds) { + @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig) { final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); @@ -432,7 +434,8 @@ class AppCompatSizeCompatModePolicy { // original container or if it's a freeform window in desktop mode. boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH) || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext) - && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM); + && newParentConfig.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FREEFORM); return shouldAllowUpscaling ? Math.min( (float) viewportW / contentW, (float) viewportH / contentH) : 1f; } diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index 19941741ed19..3a2cffbe1d85 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -422,6 +422,7 @@ class DeferredDisplayUpdater { || first.brightnessMaximum != second.brightnessMaximum || first.brightnessDefault != second.brightnessDefault || first.installOrientation != second.installOrientation + || first.isForceSdr != second.isForceSdr || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate) || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio) || !first.thermalRefreshRateThrottling.contentEquals( diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index cc6904f9b3af..156d8a065b67 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -103,7 +103,7 @@ public final class DesktopModeBoundsCalculator { final TaskDisplayArea displayArea = task.getDisplayArea(); final Rect screenBounds = displayArea.getBounds(); final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE); - if (!DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) { + if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()) { return centerInScreen(idealSize, screenBounds); } if (activity.mAppCompatController.getAppCompatAspectRatioOverrides() diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index 61fbb96882ec..da7631723185 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -35,8 +35,8 @@ public final class DesktopModeHelper { "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); /** Whether desktop mode is enabled. */ - static boolean isDesktopModeEnabled(@NonNull Context context) { - return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context); + static boolean isDesktopModeEnabled() { + return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isEnabled(); } /** @@ -60,7 +60,7 @@ public final class DesktopModeHelper { * Return {@code true} if desktop mode can be entered on the current device. */ static boolean canEnterDesktopMode(@NonNull Context context) { - return isDesktopModeEnabled(context) + return isDesktopModeEnabled() && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context)); } } diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index 5514294ed477..e007b1d07b34 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -181,22 +181,30 @@ class EmbeddedWindowController { return true; } - boolean transferToHost(@NonNull InputTransferToken embeddedWindowToken, + boolean transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken, @NonNull WindowState transferToHostWindowState) { EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken); if (!isValidTouchGestureParams(transferToHostWindowState, ew)) { return false; } + if (callingUid != ew.mOwnerUid) { + throw new SecurityException( + "Transfer request must originate from owner of transferFromToken"); + } return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(), transferToHostWindowState.mInputChannelToken); } - boolean transferToEmbedded(WindowState hostWindowState, + boolean transferToEmbedded(int callingUid, WindowState hostWindowState, @NonNull InputTransferToken transferToToken) { final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken); if (!isValidTouchGestureParams(hostWindowState, ew)) { return false; } + if (callingUid != hostWindowState.mOwnerUid) { + throw new SecurityException( + "Transfer request must originate from owner of transferFromToken"); + } return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken, ew.getInputChannelToken()); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 638e92f112c7..42ea5a88a09b 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -28,6 +28,7 @@ import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ActivityRecord.State.RESUMED; @@ -57,6 +58,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; +import com.android.server.pm.UserManagerInternal; import com.android.server.wm.LaunchParamsController.LaunchParams; import java.io.PrintWriter; @@ -1761,10 +1763,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * @return last reparented root task, or {@code null} if the root tasks had to be destroyed. */ Task remove() { + final TaskDisplayArea toDisplayArea = getReparentToTaskDisplayArea(getFocusedRootTask()); mPreferredTopFocusableRootTask = null; // TODO(b/153090332): Allow setting content removal mode per task display area final boolean destroyContentOnRemoval = mDisplayContent.shouldDestroyContentOnRemove(); - final TaskDisplayArea toDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); Task lastReparentedRootTask = null; // Root tasks could be reparented from the removed display area to other display area. After @@ -1830,6 +1832,41 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return lastReparentedRootTask; } + /** + * Returns the {@link TaskDisplayArea} to which root tasks should be reparented. + * + * <p>In the automotive multi-user multi-display environment where background users have + * UI access on their assigned displays (a.k.a. visible background users), it's not allowed to + * launch an activity on an unassigned display. If an activity is attempted to launch on an + * unassigned display, it throws an exception. + * <p>This method determines the appropriate {@link TaskDisplayArea} for reparenting root tasks + * when a display is removed, in order to avoid the exception. If the root task is null, + * the visible background user is not supported or the user associated with the root task is + * not a visible background user, it returns the default {@link TaskDisplayArea} of the default + * display. Otherwise, it returns the default {@link TaskDisplayArea} of the main display + * assigned to the user. + * + * @param rootTask The root task whose {@link TaskDisplayArea} needs to be determined. + * @return The {@link TaskDisplayArea} where the root tasks should be reparented to. + */ + private TaskDisplayArea getReparentToTaskDisplayArea(Task rootTask) { + final TaskDisplayArea defaultTaskDisplayArea = + mRootWindowContainer.getDefaultTaskDisplayArea(); + if (rootTask == null) { + return defaultTaskDisplayArea; + } + UserManagerInternal userManagerInternal = mAtmService.mWindowManager.mUmInternal; + if (!userManagerInternal.isVisibleBackgroundFullUser(rootTask.mUserId)) { + return defaultTaskDisplayArea; + } + int toDisplayId = userManagerInternal.getMainDisplayAssignedToUser(rootTask.mUserId); + if (toDisplayId == INVALID_DISPLAY) { + return defaultTaskDisplayArea; + } + DisplayContent dc = mRootWindowContainer.getDisplayContent(toDisplayId); + return dc != null ? dc.getDefaultTaskDisplayArea() : defaultTaskDisplayArea; + } + /** Whether this task display area can request orientation. */ boolean canSpecifyOrientation(@ScreenOrientation int orientation) { // Only allow to specify orientation if this TDA is the last focused one on this logical diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 9d4652957487..7c3f0f22608e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -35,6 +35,7 @@ import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; @@ -115,7 +116,6 @@ import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.AlwaysTruePredicate; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -457,7 +457,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< source.setFrame(provider.getArbitraryRectangle()) .updateSideHint(getBounds()) .setBoundingRects(provider.getBoundingRects()); - if (Flags.enableCaptionCompatInsetForceConsumption()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { source.setFlags(provider.getFlags()); } mLocalInsetsSources.put(id, source); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 33f2dd103c2e..b8f47cce6005 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9212,6 +9212,8 @@ public class WindowManagerService extends IWindowManager.Stub final InputApplicationHandle applicationHandle; final String name; Objects.requireNonNull(outInputChannel); + Objects.requireNonNull(inputTransferToken); + synchronized (mGlobalLock) { WindowState hostWindowState = hostInputTransferToken != null ? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null; @@ -9236,6 +9238,7 @@ public class WindowManagerService extends IWindowManager.Stub Objects.requireNonNull(transferFromToken); Objects.requireNonNull(transferToToken); + final int callingUid = Binder.getCallingUid(); final long identity = Binder.clearCallingIdentity(); boolean didTransfer; try { @@ -9245,12 +9248,14 @@ public class WindowManagerService extends IWindowManager.Stub // represents an embedded window so transfer from host to embedded. WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken()); if (windowStateTo != null) { - didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken, + didTransfer = mEmbeddedWindowController.transferToHost(callingUid, + transferFromToken, windowStateTo); } else { WindowState windowStateFrom = mInputToWindowMap.get( transferFromToken.getToken()); - didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom, + didTransfer = mEmbeddedWindowController.transferToEmbedded(callingUid, + windowStateFrom, transferToToken); } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 976be4aa3bd4..30d6f0a46bae 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -326,6 +326,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio public static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22; public static final int ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN = 1 << 23; public static final int ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM = 1 << 24; + public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25; public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff; /** @@ -1293,8 +1294,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio if (hasResumedFreeform && com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode() // Exclude task layer 1 because it is already the top most. - && minTaskLayer > 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) { - stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM; + && minTaskLayer > 1) { + if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) { + stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM; + } else { + stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE; + } } stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; if (visible) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 8c4448e7915f..155e73c53819 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1808,9 +1808,30 @@ void NativeInputManager::loadAdditionalMouseResources( ATRACE_CALL(); JNIEnv* env = jniEnv(); - for (int32_t iconId = static_cast<int32_t>(PointerIconStyle::TYPE_CONTEXT_MENU); - iconId <= static_cast<int32_t>(PointerIconStyle::TYPE_HANDWRITING); ++iconId) { - const PointerIconStyle pointerIconStyle = static_cast<PointerIconStyle>(iconId); + constexpr static std::array ADDITIONAL_STYLES{PointerIconStyle::TYPE_CONTEXT_MENU, + PointerIconStyle::TYPE_HAND, + PointerIconStyle::TYPE_HELP, + PointerIconStyle::TYPE_WAIT, + PointerIconStyle::TYPE_CELL, + PointerIconStyle::TYPE_CROSSHAIR, + PointerIconStyle::TYPE_TEXT, + PointerIconStyle::TYPE_VERTICAL_TEXT, + PointerIconStyle::TYPE_ALIAS, + PointerIconStyle::TYPE_COPY, + PointerIconStyle::TYPE_NO_DROP, + PointerIconStyle::TYPE_ALL_SCROLL, + PointerIconStyle::TYPE_HORIZONTAL_DOUBLE_ARROW, + PointerIconStyle::TYPE_VERTICAL_DOUBLE_ARROW, + PointerIconStyle::TYPE_TOP_RIGHT_DOUBLE_ARROW, + PointerIconStyle::TYPE_TOP_LEFT_DOUBLE_ARROW, + PointerIconStyle::TYPE_ZOOM_IN, + PointerIconStyle::TYPE_ZOOM_OUT, + PointerIconStyle::TYPE_GRAB, + PointerIconStyle::TYPE_GRABBING, + PointerIconStyle::TYPE_HANDWRITING, + PointerIconStyle::TYPE_SPOT_HOVER}; + + for (const auto pointerIconStyle : ADDITIONAL_STYLES) { PointerIcon pointerIcon = loadPointerIcon(env, displayId, pointerIconStyle); (*outResources)[pointerIconStyle] = toSpriteIcon(pointerIcon); if (!pointerIcon.bitmapFrames.empty()) { diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt index 63cf7bf7cb5e..c05c3819ca28 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt @@ -139,16 +139,15 @@ class MetadataSyncAdapterTest { runtimeSearchSession.put(putDocumentsRequest).get() staticSearchSession.put(putDocumentsRequest).get() val metadataSyncAdapter = - MetadataSyncAdapter( - testExecutor, - runtimeSearchSession, + MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + + val submitSyncRequest = + metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( staticSearchSession, - packageManager, + runtimeSearchSession, ) - val submitSyncRequest = metadataSyncAdapter.submitSyncRequest() - - assertThat(submitSyncRequest.get()).isTrue() + assertThat(submitSyncRequest).isInstanceOf(Unit::class.java) } @Test @@ -182,16 +181,15 @@ class MetadataSyncAdapterTest { PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() staticSearchSession.put(putDocumentsRequest).get() val metadataSyncAdapter = - MetadataSyncAdapter( - testExecutor, - runtimeSearchSession, + MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + + val submitSyncRequest = + metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( staticSearchSession, - packageManager, + runtimeSearchSession, ) - val submitSyncRequest = metadataSyncAdapter.submitSyncRequest() - - assertThat(submitSyncRequest.get()).isTrue() + assertThat(submitSyncRequest).isInstanceOf(Unit::class.java) } @Test @@ -239,16 +237,15 @@ class MetadataSyncAdapterTest { PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() runtimeSearchSession.put(putDocumentsRequest).get() val metadataSyncAdapter = - MetadataSyncAdapter( - testExecutor, - runtimeSearchSession, + MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + + val submitSyncRequest = + metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( staticSearchSession, - packageManager, + runtimeSearchSession, ) - val submitSyncRequest = metadataSyncAdapter.submitSyncRequest() - - assertThat(submitSyncRequest.get()).isTrue() + assertThat(submitSyncRequest).isInstanceOf(Unit::class.java) } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 8b80f85aec00..255dcb083518 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -27,6 +27,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_D import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; +import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT; @@ -195,8 +196,8 @@ public class DisplayManagerServiceTest { private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS = STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED; @@ -238,6 +239,8 @@ public class DisplayManagerServiceTest { private UserManager mUserManager; + private int[] mAllowedHdrOutputTypes; + private final DisplayManagerService.Injector mShortMockedInjector = new DisplayManagerService.Injector() { @Override @@ -256,11 +259,12 @@ public class DisplayManagerServiceTest { displayAdapterListener, flags, mMockedDisplayNotificationManager, new LocalDisplayAdapter.Injector() { - @Override - public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { - return mSurfaceControlProxy; - } - }); + @Override + public LocalDisplayAdapter.SurfaceControlProxy + getSurfaceControlProxy() { + return mSurfaceControlProxy; + } + }); } @Override @@ -320,7 +324,7 @@ public class DisplayManagerServiceTest { @Override int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, - int[] autoHdrTypes) { + int[] allowedHdrOutputTypes) { mHdrConversionMode = conversionMode; mPreferredHdrOutputType = preferredHdrOutputType; return Display.HdrCapabilities.HDR_TYPE_INVALID; @@ -1295,11 +1299,11 @@ public class DisplayManagerServiceTest { .setUniqueId("uniqueId --- mirror display"); assertThrows(SecurityException.class, () -> { localService.createVirtualDisplay( - builder.build(), - mMockAppToken /* callback */, - null /* virtualDeviceToken */, - mock(DisplayWindowPolicyController.class), - PACKAGE_NAME); + builder.build(), + mMockAppToken /* callback */, + null /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); }); } @@ -1433,7 +1437,7 @@ public class DisplayManagerServiceTest { // The virtual display should not have FLAG_ALWAYS_UNLOCKED set. assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags - & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED)); + & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED)); } /** @@ -1466,7 +1470,7 @@ public class DisplayManagerServiceTest { // The virtual display should not have FLAG_PRESENTATION set. assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags - & DisplayDeviceInfo.FLAG_PRESENTATION)); + & DisplayDeviceInfo.FLAG_PRESENTATION)); } @Test @@ -2358,6 +2362,7 @@ public class DisplayManagerServiceTest { HdrConversionMode.HDR_CONVERSION_FORCE, Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION); displayManager.setHdrConversionModeInternal(mode); + assertEquals(mode, displayManager.getHdrConversionModeSettingInternal()); assertEquals(mode.getConversionMode(), mHdrConversionMode); assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType); @@ -2402,6 +2407,86 @@ public class DisplayManagerServiceTest { } @Test + public void testSetAreUserDisabledHdrTypesAllowed_withFalse_whenHdrDisabled_stripsHdrType() { + DisplayManagerService displayManager = new DisplayManagerService( + mContext, new BasicInjector() { + @Override + int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, + int[] allowedTypes) { + mAllowedHdrOutputTypes = allowedTypes; + return Display.HdrCapabilities.HDR_TYPE_INVALID; + } + + // Overriding this method to capture the allowed HDR type + @Override + int[] getSupportedHdrOutputTypes() { + return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}; + } + }); + + // Setup: no HDR types disabled, userDisabledTypes allowed, system conversion + displayManager.setUserDisabledHdrTypesInternal(new int [0]); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(true); + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + + assertEquals(1, mAllowedHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]); + + // Action: disable Dolby Vision, set userDisabledTypes not allowed + displayManager.setUserDisabledHdrTypesInternal( + new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(false); + + assertEquals(0, mAllowedHdrOutputTypes.length); + } + + @Test + public void testGetEnabledHdrTypesLocked_whenTypesDisabled_stripsDisabledTypes() { + DisplayManagerService displayManager = new DisplayManagerService( + mContext, new BasicInjector() { + @Override + int[] getSupportedHdrOutputTypes() { + return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}; + } + }); + + displayManager.setUserDisabledHdrTypesInternal(new int [0]); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(true); + int [] enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(1, enabledHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]); + + displayManager.setAreUserDisabledHdrTypesAllowedInternal(false); + enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(1, enabledHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]); + + displayManager.setUserDisabledHdrTypesInternal( + new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}); + enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(0, enabledHdrOutputTypes.length); + } + + @Test + public void testSetHdrConversionModeInternal_isForceSdrIsUpdated() { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); + FakeDisplayDevice displayDevice = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL); + LogicalDisplay logicalDisplay = + logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true); + + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID)); + assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr); + + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr); + } + + @Test public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerInternal localService = displayManager.new LocalService(); @@ -3505,7 +3590,7 @@ public class DisplayManagerServiceTest { } private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager, - Display.Mode[] modes) { + Display.Mode[] modes) { FakeDisplayDevice displayDevice = new FakeDisplayDevice(); DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); displayDeviceInfo.supportedModes = modes; @@ -3761,9 +3846,9 @@ public class DisplayManagerServiceTest { public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) { for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) { if (mode.matchesIfValid( - preferredMode.getPhysicalWidth(), - preferredMode.getPhysicalHeight(), - preferredMode.getRefreshRate())) { + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getRefreshRate())) { mPreferredMode = mode; break; } diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index 8b653378664e..32135f1cb7fa 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -22,9 +22,10 @@ import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PRO import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -36,6 +37,7 @@ import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Process; +import android.os.RemoteException; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -137,9 +139,17 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener = spy(mSensitiveContentProtectionManagerService.mNotificationListener); - doCallRealMethod() - .when(mSensitiveContentProtectionManagerService.mNotificationListener) - .onListenerConnected(); + + // Unexpected NLS interactions when registered cause test flakes. For purposes of this test, + // the test will control any NLS calls. + try { + doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener) + .registerAsSystemService(any(), any(), anyInt()); + doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener) + .unregisterAsSystemService(); + } catch (RemoteException e) { + // Intra-process call, should never happen. + } // Setup RankingMap and two possilbe rankings when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 5ec53023dc67..f6ad07d03673 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -62,6 +62,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; +import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND; @@ -534,6 +535,14 @@ public class MockingOomAdjusterTests { updateOomAdj(app); assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP); assertEquals("perceptible-freeform-activity", app.mState.getAdjType()); + + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE + | WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) + .when(wpc).getActivityStateFlags(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, + SCHED_GROUP_FOREGROUND_WINDOW); + assertEquals("vis-multi-window-activity", app.mState.getAdjType()); } @SuppressWarnings("GuardedBy") diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java index a82658b52a77..3062d5120e6f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java @@ -16,13 +16,19 @@ package com.android.server.pm; +import static android.media.AudioAttributes.USAGE_ALARM; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.testng.AssertJUnit.assertEquals; import android.app.Notification; import android.app.NotificationManager; @@ -31,6 +37,9 @@ import android.content.pm.UserInfo; import android.media.AudioAttributes; import android.media.AudioFocusInfo; import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.PlayerProxy; +import android.media.audiopolicy.AudioPolicy; import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; @@ -45,6 +54,10 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + @RunWith(JUnit4.class) public class BackgroundUserSoundNotifierTest { @@ -63,7 +76,10 @@ public class BackgroundUserSoundNotifierTest { MockitoAnnotations.initMocks(this); mSpiedContext = spy(mRealContext); mUsersToRemove = new ArraySet<>(); - mUserManager = UserManager.get(mRealContext); + + mUserManager = spy(mSpiedContext.getSystemService(UserManager.class)); + doReturn(mUserManager) + .when(mSpiedContext).getSystemService(UserManager.class); doReturn(mNotificationManager) .when(mSpiedContext).getSystemService(NotificationManager.class); mBackgroundUserSoundNotifier = new BackgroundUserSoundNotifier(mSpiedContext); @@ -74,12 +90,9 @@ public class BackgroundUserSoundNotifierTest { mUsersToRemove.stream().toList().forEach(this::removeUser); } @Test - public void testAlarmOnBackgroundUser_ForegroundUserNotified() throws RemoteException { - AudioAttributes aa = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ALARM).build(); - UserInfo user = createUser("User", - UserManager.USER_TYPE_FULL_SECONDARY, - 0); + public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException { + AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); + UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0); final int fgUserId = mSpiedContext.getUserId(); final int bgUserUid = user.id * 100000; doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser(); @@ -95,10 +108,9 @@ public class BackgroundUserSoundNotifierTest { } @Test - public void testMediaOnBackgroundUser_ForegroundUserNotNotified() throws RemoteException { + public void testMediaOnBackgroundUser_foregroundUserNotNotified() throws RemoteException { AudioAttributes aa = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA).build(); - UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0); final int bgUserUid = mSpiedContext.getUserId() * 100000; AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN, @@ -109,9 +121,9 @@ public class BackgroundUserSoundNotifierTest { } @Test - public void testAlarmOnForegroundUser_ForegroundUserNotNotified() throws RemoteException { + public void testAlarmOnForegroundUser_foregroundUserNotNotified() throws RemoteException { AudioAttributes aa = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ALARM).build(); + .setUsage(USAGE_ALARM).build(); final int fgUserId = mSpiedContext.getUserId(); final int fgUserUid = fgUserId * 100000; doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser(); @@ -123,6 +135,109 @@ public class BackgroundUserSoundNotifierTest { verifyZeroInteractions(mNotificationManager); } + @Test + public void testMuteAlarmSounds() { + final int fgUserId = mSpiedContext.getUserId(); + int bgUserId = fgUserId + 1; + int bgUserUid = bgUserId * 100000; + mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid; + + AudioManager mockAudioManager = mock(AudioManager.class); + when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager); + + AudioPlaybackConfiguration apc1 = mock(AudioPlaybackConfiguration.class); + when(apc1.getClientUid()).thenReturn(bgUserUid); + when(apc1.getPlayerProxy()).thenReturn(mock(PlayerProxy.class)); + + AudioPlaybackConfiguration apc2 = mock(AudioPlaybackConfiguration.class); + when(apc2.getClientUid()).thenReturn(bgUserUid + 1); + when(apc2.getPlayerProxy()).thenReturn(mock(PlayerProxy.class)); + + List<AudioPlaybackConfiguration> configs = new ArrayList<>(); + configs.add(apc1); + configs.add(apc2); + when(mockAudioManager.getActivePlaybackConfigurations()).thenReturn(configs); + + AudioPolicy mockAudioPolicy = mock(AudioPolicy.class); + + AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); + AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", /* packageName= */ "", + AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, + Build.VERSION.SDK_INT); + Stack<AudioFocusInfo> focusStack = new Stack<>(); + focusStack.add(afi); + doReturn(focusStack).when(mockAudioPolicy).getFocusStack(); + mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy; + + mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext); + + verify(apc1.getPlayerProxy()).stop(); + verify(apc2.getPlayerProxy(), never()).stop(); + } + + @Test + public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() { + final int fgUserId = mSpiedContext.getUserId(); + UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0); + int bgUserUid = bgUser.id * 100000; + + AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); + AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", "", + AudioManager.AUDIOFOCUS_GAIN, 0, 0, Build.VERSION.SDK_INT); + + mBackgroundUserSoundNotifier.getAudioPolicyFocusListener() + .onAudioFocusGrant(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + + verify(mNotificationManager) + .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()), + eq(afi.getClientUid()), any(Notification.class), + eq(UserHandle.of(fgUserId))); + } + + + @Test + public void testCreateNotification_UserSwitcherEnabled_bothActionsAvailable() { + String userName = "BgUser"; + + doReturn(true).when(mUserManager).isUserSwitcherEnabled(); + doReturn(UserManager.SWITCHABILITY_STATUS_OK) + .when(mUserManager).getUserSwitchability(any()); + + Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, + mSpiedContext); + + assertEquals("Alarm for BgUser", notification.extras.getString( + Notification.EXTRA_TITLE)); + assertEquals(Notification.CATEGORY_REMINDER, notification.category); + assertEquals(Notification.VISIBILITY_PUBLIC, notification.visibility); + assertEquals(com.android.internal.R.drawable.ic_audio_alarm, + notification.getSmallIcon().getResId()); + + assertEquals(2, notification.actions.length); + assertEquals(mSpiedContext.getString( + com.android.internal.R.string.bg_user_sound_notification_button_mute), + notification.actions[0].title); + assertEquals(mSpiedContext.getString( + com.android.internal.R.string.bg_user_sound_notification_button_switch_user), + notification.actions[1].title); + } + + @Test + public void testCreateNotification_UserSwitcherDisabled_onlyMuteActionAvailable() { + String userName = "BgUser"; + + doReturn(false).when(mUserManager).isUserSwitcherEnabled(); + doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED) + .when(mUserManager).getUserSwitchability(any()); + + Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, + mSpiedContext); + + assertEquals(1, notification.actions.length); + assertEquals(mSpiedContext.getString( + com.android.internal.R.string.bg_user_sound_notification_button_mute), + notification.actions[0].title); + } private UserInfo createUser(String name, String userType, int flags) { UserInfo user = mUserManager.createUser(name, userType, flags); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 15ae4634b573..0b762df86df9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -290,7 +290,7 @@ public class WallpaperManagerServiceTests { final WallpaperData fallbackData = mService.mFallbackWallpaper; assertEquals("Fallback wallpaper component should be ImageWallpaper.", - sImageWallpaperComponentName, fallbackData.wallpaperComponent); + sImageWallpaperComponentName, fallbackData.getComponent()); verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent); verifyDisplayData(); @@ -580,7 +580,7 @@ public class WallpaperManagerServiceTests { final WallpaperData lastData = mService.mLastWallpaper; assertNotNull("Last wallpaper must not be null", lastData); assertEquals("Last wallpaper component must be equals.", expectedComponent, - lastData.wallpaperComponent); + lastData.getComponent()); assertEquals("The user id in last wallpaper should be the last switched user", lastUserId, lastData.userId); assertNotNull("Must exist user data connection on last wallpaper data", diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 44aa868716eb..a55aa2364aa5 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -30,7 +30,6 @@ import android.graphics.fonts.SystemFonts; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.system.Os; @@ -41,8 +40,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.text.flags.Flags; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -1106,7 +1103,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1126,7 +1122,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1); @@ -1146,7 +1141,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1166,7 +1160,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1186,7 +1179,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1206,7 +1198,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1226,7 +1217,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1246,7 +1236,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1266,7 +1255,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1286,7 +1274,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1); @@ -1306,7 +1293,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1326,7 +1312,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1346,7 +1331,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1366,7 +1350,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1386,7 +1369,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1406,7 +1388,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1426,7 +1407,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1446,7 +1426,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1466,7 +1445,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1486,7 +1464,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1506,7 +1483,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1527,7 +1503,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1548,7 +1523,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1569,7 +1543,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java index 4d2396c78d16..65b4ac116b34 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -104,6 +104,12 @@ public class VibratorHelperTest extends UiServiceTestCase { } @Test + public void createVibrationEffectFromSoundUri_opaqueUri() { + Uri uri = Uri.parse("a:b#c"); + assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri)); + } + + @Test public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() { Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI; assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri)); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index f74340113a04..7bce8285972c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1640,7 +1640,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); - mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); assertFalse(mActivity.inSizeCompatMode()); // Resize app to make original app bounds larger than parent bounds. @@ -1667,7 +1667,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); - mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); assertFalse(mActivity.inSizeCompatMode()); // Resize app to make original app bounds smaller than parent bounds. @@ -1692,7 +1692,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); - mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); assertFalse(mActivity.inSizeCompatMode()); final Rect originalAppBounds = mActivity.getBounds(); @@ -1705,6 +1705,38 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(originalAppBounds, mActivity.getBounds()); } + /** + * Test that when desktop mode is enabled, a freeform unresizeable activity is not up-scaled + * when exiting freeform despite its larger parent bounds. + */ + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void testCompatScaling_freeformUnresizeableApp_exitFreeform_notScaled() { + doReturn(true).when(() -> + DesktopModeHelper.canEnterDesktopMode(any())); + final int dw = 600; + final int dh = 800; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); + final Rect originalAppBounds = mActivity.getBounds(); + + assertFalse(mActivity.inSizeCompatMode()); + + // Resize app to make original app bounds smaller than parent bounds. + mTask.getWindowConfiguration().setAppBounds( + new Rect(0, 0, dw + 300, dh + 400)); + // Change windowing mode from freeform to fullscreen + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + // App should enter size compat mode but remain its original size. + assertTrue(mActivity.inSizeCompatMode()); + assertEquals(originalAppBounds, mActivity.getBounds()); + } + @Test public void testGetLetterboxInnerBounds_noScalingApplied() { // Set up a display in portrait and ignoring orientation request. diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 41223db750c0..2ef057350033 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5038,7 +5038,7 @@ public class CarrierConfigManager { * {@code true} - Enable NI SUPL message injection. */ @FlaggedApi(android.location.flags.Flags - .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG) + .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG_BUGFIX) public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = KEY_PREFIX + "enable_ni_supl_message_injection_bool"; @@ -5059,7 +5059,7 @@ public class CarrierConfigManager { defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, SUPL_EMERGENCY_MODE_TYPE_CP_ONLY); defaults.putStringArray(KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, null); - if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { defaults.putBoolean(KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL, false); } return defaults; diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index c2e71f8269da..2a82d5f9bd7c 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -21,6 +21,8 @@ import android.Manifest import android.content.Context import android.content.ContextWrapper import android.content.PermissionChecker +import android.content.pm.PackageManager +import android.content.pm.PackageManagerInternal import android.hardware.display.DisplayManager import android.hardware.display.DisplayViewport import android.hardware.display.VirtualDisplay @@ -29,10 +31,13 @@ import android.hardware.input.InputManagerGlobal import android.os.InputEventInjectionSync import android.os.SystemClock import android.os.test.TestLooper +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit +import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings import android.view.View.OnKeyListener import android.view.InputDevice +import android.view.KeyCharacterMap import android.view.KeyEvent import android.view.SurfaceHolder import android.view.SurfaceView @@ -94,6 +99,10 @@ class InputManagerServiceTests { ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java) .mockStatic(PermissionChecker::class.java).build()!! + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + @get:Rule val fakeSettingsProviderRule = FakeSettingsProvider.rule()!! @@ -107,8 +116,14 @@ class InputManagerServiceTests { private lateinit var windowManagerInternal: WindowManagerInternal @Mock + private lateinit var packageManagerInternal: PackageManagerInternal + + @Mock private lateinit var uEventManager: UEventManager + @Mock + private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface + private lateinit var service: InputManagerService private lateinit var localService: InputManagerInternal private lateinit var context: Context @@ -135,15 +150,29 @@ class InputManagerServiceTests { override fun registerLocalService(service: InputManagerInternal?) { localService = service!! } + + override fun getKeyboardBacklightController( + nativeService: NativeInputManagerService?, + dataStore: PersistentDataStore? + ): InputManagerService.KeyboardBacklightControllerInterface { + return kbdController + } }) inputManagerGlobalSession = InputManagerGlobal.createTestSession(service) val inputManager = InputManager(context) whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager) whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager) + whenever(context.checkCallingOrSelfPermission(Manifest.permission.MANAGE_KEY_GESTURES)) + .thenReturn( + PackageManager.PERMISSION_GRANTED + ) ExtendedMockito.doReturn(windowManagerInternal).`when` { LocalServices.getService(eq(WindowManagerInternal::class.java)) } + ExtendedMockito.doReturn(packageManagerInternal).`when` { + LocalServices.getService(eq(PackageManagerInternal::class.java)) + } assertTrue("Local service must be registered", this::localService.isInitialized) service.setWindowManagerCallbacks(wmCallbacks) @@ -442,6 +471,37 @@ class InputManagerServiceTests { verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) } + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun handleKeyGestures_keyboardBacklight() { + service.systemRunning() + + val backlightDownEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN) + service.interceptKeyBeforeDispatching(null, backlightDownEvent, /* policyFlags = */0) + verify(kbdController).decrementKeyboardBacklight(anyInt()) + + val backlightUpEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP) + service.interceptKeyBeforeDispatching(null, backlightUpEvent, /* policyFlags = */0) + verify(kbdController).incrementKeyboardBacklight(anyInt()) + } + + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun handleKeyGestures_toggleCapsLock() { + service.systemRunning() + + val metaDownEvent = createKeyEvent(KeyEvent.KEYCODE_META_LEFT) + service.interceptKeyBeforeDispatching(null, metaDownEvent, /* policyFlags = */0) + val altDownEvent = + createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_DOWN) + service.interceptKeyBeforeDispatching(null, altDownEvent, /* policyFlags = */0) + val altUpEvent = + createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_UP) + service.interceptKeyBeforeDispatching(null, altUpEvent, /* policyFlags = */0) + + verify(native).toggleCapsLock(anyInt()) + } + fun overrideSendActionKeyEventsToFocusedWindow( hasPermission: Boolean, hasPrivateFlag: Boolean @@ -476,6 +536,25 @@ class InputManagerServiceTests { ) whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info) } + + private fun createKeyEvent( + keycode: Int, + modifierState: Int = 0, + action: Int = KeyEvent.ACTION_DOWN + ): KeyEvent { + return KeyEvent( + /* downTime = */0, + /* eventTime = */0, + action, + keycode, + /* repeat = */0, + modifierState, + KeyCharacterMap.VIRTUAL_KEYBOARD, + /* scancode = */0, + /* flags = */0, + InputDevice.SOURCE_KEYBOARD + ) + } } private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall) diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java index 681b7f28451b..b3a998ebca0d 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java @@ -16,6 +16,7 @@ package com.android.server.input.debug; +import static android.view.InputDevice.SOURCE_MOUSE; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static org.junit.Assert.assertEquals; @@ -29,7 +30,9 @@ import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; +import android.hardware.input.InputManager; import android.testing.TestableContext; +import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -60,13 +63,15 @@ import org.mockito.MockitoAnnotations; */ @RunWith(AndroidJUnit4.class) public class TouchpadDebugViewTest { - private static final int TOUCHPAD_DEVICE_ID = 6; + private static final int TOUCHPAD_DEVICE_ID = 60; private TouchpadDebugView mTouchpadDebugView; private WindowManager.LayoutParams mWindowLayoutParams; @Mock WindowManager mWindowManager; + @Mock + InputManager mInputManager; Rect mWindowBounds; WindowMetrics mWindowMetrics; @@ -79,12 +84,21 @@ public class TouchpadDebugViewTest { mTestableContext = new TestableContext(context); mTestableContext.addMockSystemService(WindowManager.class, mWindowManager); + mTestableContext.addMockSystemService(InputManager.class, mInputManager); mWindowBounds = new Rect(0, 0, 2560, 1600); mWindowMetrics = new WindowMetrics(mWindowBounds, new WindowInsets(mWindowBounds), 1.0f); when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + InputDevice inputDevice = new InputDevice.Builder() + .setId(TOUCHPAD_DEVICE_ID) + .setSources(InputDevice.SOURCE_TOUCHPAD | SOURCE_MOUSE) + .setName("Test Device " + TOUCHPAD_DEVICE_ID) + .build(); + + when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice); + mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID, new TouchpadHardwareProperties.Builder(0f, 0f, 500f, 500f, 45f, 47f, -4f, 5f, (short) 10, true, @@ -341,4 +355,43 @@ public class TouchpadDebugViewTest { mTouchpadDebugView.updateGestureInfo(gestureType, TOUCHPAD_DEVICE_ID); assertEquals(child.getText().toString(), TouchpadDebugView.getGestureText(gestureType)); } -} + + @Test + public void testTwoFingerDrag() { + float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10; + float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10; + + // Simulate ACTION_DOWN event (gesture starts). + MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f) + ) + .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionDown); + + // Simulate ACTION_MOVE event (dragging with two fingers, processed as one pointer). + MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f + offsetX) + .y(40f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionMove); + + // Simulate ACTION_UP event (gesture ends). + MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f + offsetX) + .y(40f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionUp); + + // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture). + verify(mWindowManager, times(0)).updateViewLayout(any(), any()); + } +}
\ No newline at end of file diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp index 5a7f12f56655..90998e67ae31 100644 --- a/tests/Tracing/Android.bp +++ b/tests/Tracing/Android.bp @@ -15,7 +15,7 @@ android_test { }, // Include some source files directly to be able to access package members srcs: ["src/**/*.java"], - libs: ["android.test.runner"], + libs: ["android.test.runner.stubs.system"], static_libs: [ "junit", "androidx.test.rules", diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt index 290e7be9f6c4..94674348df08 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt @@ -22,6 +22,7 @@ import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.aidl.EnforcePermissionDetector import com.google.android.lint.aidl.PermissionAnnotationDetector import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector +import com.google.android.lint.aidl.SimpleRequiresNoPermissionDetector import com.google.auto.service.AutoService @AutoService(IssueRegistry::class) @@ -34,6 +35,7 @@ class AndroidGlobalIssueRegistry : IssueRegistry() { EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION, PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT, + SimpleRequiresNoPermissionDetector.ISSUE_SIMPLE_REQUIRES_NO_PERMISSION, ) override val api: Int diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt new file mode 100644 index 000000000000..1a13c0280ec6 --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt @@ -0,0 +1,118 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import org.jetbrains.uast.UastCallKind +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.visitor.AbstractUastVisitor + +/** + * Ensures all AIDL implementations hosted by system_server which don't call other methods are + * annotated with @RequiresNoPermission. AIDL Interfaces part of `exemptAidlInterfaces` are skipped + * during this search to ensure the detector targets only new AIDL Interfaces. + */ +class SimpleRequiresNoPermissionDetector : AidlImplementationDetector() { + override fun visitAidlMethod( + context: JavaContext, + node: UMethod, + interfaceName: String, + body: UBlockExpression + ) { + if (!isSystemServicePath(context)) return + if (context.evaluator.isAbstract(node)) return + + val fullyQualifiedInterfaceName = + getContainingAidlInterfaceQualified(context, node) ?: return + if (exemptAidlInterfaces.contains(fullyQualifiedInterfaceName)) return + + if (node.hasAnnotation(ANNOTATION_REQUIRES_NO_PERMISSION)) return + + if (!isCallingMethod(node)) { + context.report( + ISSUE_SIMPLE_REQUIRES_NO_PERMISSION, + node, + context.getLocation(node), + """ + Method ${node.name} doesn't perform any permission checks, meaning it should \ + be annotated with @RequiresNoPermission. + """.trimMargin() + ) + } + } + + private fun isCallingMethod(node: UMethod): Boolean { + val uCallExpressionVisitor = UCallExpressionVisitor() + node.accept(uCallExpressionVisitor) + + return uCallExpressionVisitor.isCallingMethod + } + + /** + * Visits the body of a `UMethod` and determines if it encounters a `UCallExpression` which is + * a `UastCallKind.METHOD_CALL`. `isCallingMethod` will hold the result of the search procedure. + */ + private class UCallExpressionVisitor : AbstractUastVisitor() { + var isCallingMethod = false + + override fun visitElement(node: UElement): Boolean { + // Stop the search early when a method call has been found. + return isCallingMethod + } + + override fun visitCallExpression(node: UCallExpression): Boolean { + if (node.kind != UastCallKind.METHOD_CALL) return false + + isCallingMethod = true + return true + } + } + + companion object { + + private val EXPLANATION = """ + Method implementations of AIDL Interfaces hosted by the `system_server` which do not + call any other methods should be annotated with @RequiresNoPermission. That is because + not calling any other methods implies that the method does not perform any permission + checking. + + Please migrate to an @RequiresNoPermission annotation. + """.trimIndent() + + @JvmField + val ISSUE_SIMPLE_REQUIRES_NO_PERMISSION = Issue.create( + id = "SimpleRequiresNoPermission", + briefDescription = "System Service APIs not calling other methods should use @RNP", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 5, + severity = Severity.ERROR, + implementation = Implementation( + SimpleRequiresNoPermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + ) + } +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt index 92d0829911bf..824be9309dbc 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt @@ -17,7 +17,6 @@ package com.google.android.lint.aidl import com.android.tools.lint.checks.infrastructure.LintDetectorTest -import com.android.tools.lint.checks.infrastructure.TestFile import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue @@ -64,7 +63,7 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { """ package com.android.server; public class Bar extends IBar.Stub { - public void testMethod() { } + public void testMethod(int parameter1, int parameter2) { } } """ ) @@ -75,8 +74,8 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { .expect( """ src/frameworks/base/services/java/com/android/server/Bar.java:3: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] - public void testMethod() { } - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + public void testMethod(int parameter1, int parameter2) { } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 errors, 0 warnings """ ) @@ -90,7 +89,7 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { """ package com.android.server; public class Bar extends IBar.Stub { - public void testMethod() { } + public void testMethod(int parameter1, int parameter2) { } } """ ) @@ -132,7 +131,7 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { """ package com.android.server; public abstract class Bar extends IBar.Stub { - public abstract void testMethod(); + public abstract void testMethod(int parameter1, int parameter2); } """ ) @@ -177,50 +176,6 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { .expectClean() } - /* Stubs */ - - // A service with permission annotation on the method. - private val interfaceIFoo: TestFile = java( - """ - public interface IFoo extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IFoo { - } - @Override - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public void testMethod(); - @Override - @android.annotation.RequiresNoPermission - public void testMethodNoPermission(); - @Override - @android.annotation.PermissionManuallyEnforced - public void testMethodManual(); - } - """ - ).indented() - - // A service with no permission annotation. - private val interfaceIBar: TestFile = java( - """ - public interface IBar extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IBar { - } - public void testMethod(); - } - """ - ).indented() - - // A service whose AIDL Interface is exempted. - private val interfaceIExempted: TestFile = java( - """ - package android.accessibilityservice; - public interface IBrailleDisplayConnection extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection { - } - public void testMethod(); - } - """ - ).indented() - private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted) private fun createVisitedPath(filename: String) = diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt new file mode 100644 index 000000000000..a33b48c7eaa0 --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt @@ -0,0 +1,244 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +class SimpleRequiresNoPermissionDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = SimpleRequiresNoPermissionDetector() + override fun getIssues(): List<Issue> = listOf( + SimpleRequiresNoPermissionDetector + .ISSUE_SIMPLE_REQUIRES_NO_PERMISSION + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk() + + fun testRequiresNoPermissionUsedCorrectly_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Foo.java"), + """ + package com.android.server; + public class Foo extends IFoo.Stub { + private int memberInt; + + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(int parameter1, int parameter2) { + if (parameter1 < parameter2) { + memberInt = parameter1; + } else { + memberInt = parameter2; + } + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingRequiresNoPermission_shouldWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends IBar.Stub { + private int memberInt; + + @Override + public void testMethod(int parameter1, int parameter2) { + if (parameter1 < parameter2) { + memberInt = parameter1; + } else { + memberInt = parameter2; + } + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expect( + """ + src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission] + @Override + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testMethodOnlyPerformsConstructorCall_shouldWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends IBar.Stub { + private IntPair memberIntPair; + + @Override + public void testMethod(int parameter1, int parameter2) { + memberIntPair = new IntPair(parameter1, parameter2); + } + + private static class IntPair { + public int first; + public int second; + + public IntPair(int first, int second) { + this.first = first; + this.second = second; + } + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expect( + """ + src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission] + @Override + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testMissingRequiresNoPermissionInIgnoredDirectory_shouldNotWarn() { + lint() + .files( + java( + ignoredPath, + """ + package com.android.server; + public class Bar extends IBar.Stub { + @Override + public void testMethod(int parameter1, int parameter2) {} + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingRequiresNoPermissionAbstractMethod_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public abstract class Bar extends IBar.Stub { + private int memberInt; + + @Override + public abstract void testMethodNoPermission(int parameter1, int parameter2); + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + // If this test fails, consider the following steps: + // 1. Pick the first entry (interface) from `exemptAidlInterfaces`. + // 2. Change `interfaceIExempted` to use that interface. + // 3. Change this test's class to extend the interface's Stub. + fun testMissingRequiresNoPermissionAidlInterfaceExempted_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends android.accessibilityservice.IBrailleDisplayConnection.Stub { + public void testMethod(int parameter1, int parameter2) {} + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMethodMakesAnotherMethodCall_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends IBar.Stub { + private int memberInt; + + @Override + public void testMethod(int parameter1, int parameter2) { + if (!hasPermission()) return; + + if (parameter1 < parameter2) { + memberInt = parameter1; + } else { + memberInt = parameter2; + } + } + + private bool hasPermission() { + // Perform a permission check. + return true; + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted) + + private fun createVisitedPath(filename: String) = + "src/frameworks/base/services/java/com/android/server/$filename" + + private val ignoredPath = "src/test/pkg/TestClass.java" +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt index 2ec8fddbb4e9..18a8f186b624 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt @@ -85,4 +85,46 @@ val manifestStub: TestFile = java( } } """.trimIndent() -)
\ No newline at end of file +) + +// A service with permission annotation on the method. +val interfaceIFoo: TestFile = java( + """ + public interface IFoo extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IFoo { + } + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(int parameter1, int parameter2); + @Override + @android.annotation.PermissionManuallyEnforced + public void testMethodManual(); + } + """ +).indented() + +// A service with no permission annotation. +val interfaceIBar: TestFile = java( + """ + public interface IBar extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBar { + } + public void testMethod(int parameter1, int parameter2); + } + """ +).indented() + +// A service whose AIDL Interface is exempted. +val interfaceIExempted: TestFile = java( + """ + package android.accessibilityservice; + public interface IBrailleDisplayConnection extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection { + } + public void testMethod(); + } + """ +).indented() |