diff options
228 files changed, 5170 insertions, 1806 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/app/notification.aconfig b/core/java/android/app/notification.aconfig index 9891e8930936..9b06adf4e894 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -236,4 +236,12 @@ flag { namespace: "systemui" description: "Guards new android.app.richongoingnotification api" bug: "337261753" +} + +flag { + name: "ui_rich_ongoing" + is_exported: true + namespace: "systemui" + description: "Guards new android.app.richongoingnotification promotion and new uis" + bug: "337261753" }
\ No newline at end of file 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 71ceaf75a1a9..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, 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/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/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index e440dc9053fd..4db9ddf128da 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -223,17 +223,17 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto "ServiceManager returned a null ProtoLog Configuration Service"); try { - var args = new ProtoLogConfigurationService.RegisterClientArgs(); + var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs(); if (viewerConfigFilePath != null) { args.setViewerConfigFile(viewerConfigFilePath); } final var groupArgs = Stream.of(groups) - .map(group -> new ProtoLogConfigurationService.RegisterClientArgs + .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(group.name(), group.isLogToLogcat())) - .toArray( - ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new); + .toArray(ProtoLogConfigurationServiceImpl + .RegisterClientArgs.GroupConfig[]::new); args.setGroups(groupArgs); mProtoLogConfigurationService.registerClient(this, args); diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java index 7031d694f09c..d65aaae7deaa 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java @@ -16,434 +16,32 @@ package com.android.internal.protolog; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; - import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemService; -import android.content.Context; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; -import android.tracing.perfetto.DataSourceParams; -import android.tracing.perfetto.InitArguments; -import android.tracing.perfetto.Producer; -import android.util.Log; -import android.util.proto.ProtoInputStream; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/** - * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing - * system. Currently this service has the following roles: - * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. - * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog - * clients. This is for two reasons: firstly, because client processes might be frozen so might - * not response to the request to dump their viewer config when the trace is stopped; secondly, - * multiple processes might be running the same code with the same viewer config, this centralized - * service ensures we don't dump the same viewer config multiple times across processes. - * <p> - * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to - * this service on initialization. - * <p> - * This service is intended to run on the system server, such that it never gets frozen. - */ -@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) -public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub { - private static final String LOG_TAG = "ProtoLogConfigurationService"; - - private final ProtoLogDataSource mDataSource; - - /** - * Keeps track of how many of each viewer config file is currently registered. - * Use to keep track of which viewer config files are actively being used in tracing and might - * need to be dumped on flush. - */ - private final Map<String, Integer> mConfigFileCounts = new HashMap<>(); - /** - * Keeps track of the viewer config file of each client if available. - */ - private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>(); - - /** - * Keeps track of all the protolog groups that have been registered by clients and are still - * being actively traced. - */ - private final Set<String> mRegisteredGroups = new HashSet<>(); - /** - * Keeps track of all the clients that are actively tracing a given protolog group. - */ - private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>(); - - /** - * Keeps track of whether or not a given group should be logged to logcat. - * True when logging to logcat, false otherwise. - */ - private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>(); - - /** - * Keeps track of all the tracing instance ids that are actively running for ProtoLog. - */ - private final Set<Integer> mRunningInstances = new HashSet<>(); - - private final ViewerConfigFileTracer mViewerConfigFileTracer; - - public ProtoLogConfigurationService() { - this(ProtoLogDataSource::new, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { - this(dataSourceBuilder, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) { - this(ProtoLogDataSource::new, tracer); - } - - @VisibleForTesting - public ProtoLogConfigurationService( - @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, - @NonNull ViewerConfigFileTracer tracer) { - mDataSource = dataSourceBuilder.build( - this::onTracingInstanceStart, - this::onTracingInstanceFlush, - this::onTracingInstanceStop - ); - - // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be - // receive the lifecycle callbacks of the datasource and write the viewer configs if and - // when required to the datasource. - Producer.init(InitArguments.DEFAULTS); - final var params = new DataSourceParams.Builder() - .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) - .build(); - mDataSource.register(params); - - mViewerConfigFileTracer = tracer; - } - - public static class RegisterClientArgs extends IRegisterClientArgs.Stub { - /** - * The viewer config file to be registered for this client ProtoLog process. - */ - @Nullable - private String mViewerConfigFile = null; - /** - * The list of all groups that this client protolog process supports and might trace. - */ - @NonNull - private String[] mGroups = new String[0]; - /** - * The default logcat status of the ProtoLog client. True is logging to logcat, false - * otherwise. The indices should match the indices in {@link mGroups}. - */ - @NonNull - private boolean[] mLogcatStatus = new boolean[0]; - - public record GroupConfig(@NonNull String group, boolean logToLogcat) {} - - /** - * Specify groups to register with this client that will be used for protologging in this - * process. - * @param groups to register with this client. - * @return self - */ - public RegisterClientArgs setGroups(GroupConfig... groups) { - mGroups = new String[groups.length]; - mLogcatStatus = new boolean[groups.length]; - - for (int i = 0; i < groups.length; i++) { - mGroups[i] = groups[i].group; - mLogcatStatus[i] = groups[i].logToLogcat; - } - - return this; - } - - /** - * Set the viewer config file that the logs in this process are using. - * @param viewerConfigFile The file path of the viewer config. - * @return self - */ - public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { - mViewerConfigFile = viewerConfigFile; - - return this; - } - - @Override - @NonNull - public String[] getGroups() { - return mGroups; - } - - @Override - @NonNull - public boolean[] getGroupsDefaultLogcatStatus() { - return mLogcatStatus; - } - - @Nullable - @Override - public String getViewerConfigFile() { - return mViewerConfigFile; - } - } - - @FunctionalInterface - public interface ViewerConfigFileTracer { - /** - * Write the viewer config data to the trace buffer. - * - * @param dataSource The target datasource to write the viewer config to. - * @param viewerConfigFilePath The path of the viewer config file which contains the data we - * want to write to the trace buffer. - * @throws FileNotFoundException if the viewerConfigFilePath is invalid. - */ - void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); - } - - @Override - public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) - throws RemoteException { - client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); - - final String viewerConfigFile = args.getViewerConfigFile(); - if (viewerConfigFile != null) { - registerViewerConfigFile(client, viewerConfigFile); - } - - registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); - } - - @Override - public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, - @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, - @NonNull ResultReceiver resultReceiver) throws RemoteException { - new ProtoLogCommandHandler(this) - .exec(this, in, out, err, args, callback, resultReceiver); - } +public interface ProtoLogConfigurationService extends IProtoLogConfigurationService { /** * Get the list of groups clients have registered to the protolog service. * @return The list of ProtoLog groups registered with this service. */ @NonNull - public String[] getGroups() { - return mRegisteredGroups.toArray(new String[0]); - } + String[] getGroups(); + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + boolean isLoggingToLogcat(@NonNull String group); /** * Enable logging target groups to logcat. * @param groups we want to enable logging them to logcat for. */ - public void enableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(true, groups); - } + void enableProtoLogToLogcat(@NonNull String... groups); /** * Disable logging target groups to logcat. * @param groups we want to disable from being logged to logcat. */ - public void disableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(false, groups); - } - - /** - * Check if a group is logging to logcat - * @param group The group we want to check for - * @return True iff we are logging this group to logcat. - */ - public boolean isLoggingToLogcat(@NonNull String group) { - final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); - - if (isLoggingToLogcat == null) { - throw new RuntimeException( - "Trying to get logcat logging status of non-registered group " + group); - } - - return isLoggingToLogcat; - } - - private void registerViewerConfigFile( - @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { - final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); - mConfigFileCounts.put(viewerConfigFile, count + 1); - mClientConfigFiles.put(client, viewerConfigFile); - } - - private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, - @NonNull boolean[] logcatStatuses) throws RemoteException { - if (groups.length != logcatStatuses.length) { - throw new RuntimeException( - "Expected groups and logcatStatuses to have the same length, " - + "but groups has length " + groups.length - + " and logcatStatuses has length " + logcatStatuses.length); - } - - for (int i = 0; i < groups.length; i++) { - String group = groups[i]; - boolean logcatStatus = logcatStatuses[i]; - - mRegisteredGroups.add(group); - - mGroupToClients.putIfAbsent(group, new HashSet<>()); - mGroupToClients.get(group).add(client); - - if (!mLogGroupToLogcatStatus.containsKey(group)) { - mLogGroupToLogcatStatus.put(group, logcatStatus); - } - - boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); - if (requestedLogToLogcat != logcatStatus) { - client.toggleLogcat(requestedLogToLogcat, new String[] { group }); - } - } - } - - private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { - final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>(); - - for (String group : groups) { - final var clients = mGroupToClients.get(group); - - if (clients == null) { - // No clients associated to this group - Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group - + " with no registered clients."); - continue; - } - - for (IProtoLogClient client : clients) { - clientToGroups.putIfAbsent(client, new HashSet<>()); - clientToGroups.get(client).add(group); - } - } - - for (IProtoLogClient client : clientToGroups.keySet()) { - try { - client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); - } catch (RemoteException e) { - throw new RuntimeException( - "Failed to toggle logcat status for groups on client", e); - } - } - - for (String group : groups) { - mLogGroupToLogcatStatus.put(group, enabled); - } - } - - private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.add(instanceIdx); - } - - private void onTracingInstanceFlush() { - for (String fileName : mConfigFileCounts.keySet()) { - mViewerConfigFileTracer.trace(mDataSource, fileName); - } - } - - private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.remove(instanceIdx); - } - - private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, - @NonNull String viewerConfigFilePath) { - Utils.dumpViewerConfig(dataSource, () -> { - try { - return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); - } catch (FileNotFoundException e) { - throw new RuntimeException( - "Failed to load viewer config file " + viewerConfigFilePath, e); - } - }); - } - - private void onClientBinderDeath(@NonNull IProtoLogClient client) { - // Dump the tracing config now if no other client is going to dump the same config file. - String configFile = mClientConfigFiles.get(client); - if (configFile != null) { - final var newCount = mConfigFileCounts.get(configFile) - 1; - mConfigFileCounts.put(configFile, newCount); - boolean lastProcessWithViewerConfig = newCount == 0; - if (lastProcessWithViewerConfig) { - mViewerConfigFileTracer.trace(mDataSource, configFile); - } - } - } - - private static void writeViewerConfigGroup( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inGroupToken = pis.start(GROUPS); - final long outGroupToken = os.start(GROUPS); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) ID -> { - int id = pis.readInt(ID); - os.write(ID, id); - } - case (int) NAME -> { - String name = pis.readString(NAME); - os.write(NAME, name); - } - case (int) TAG -> { - String tag = pis.readString(TAG); - os.write(TAG, tag); - } - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inGroupToken); - os.end(outGroupToken); - } - - private static void writeViewerConfigMessage( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inMessageToken = pis.start(MESSAGES); - final long outMessagesToken = os.start(MESSAGES); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MESSAGE_ID -> os.write(MESSAGE_ID, - pis.readLong(MESSAGE_ID)); - case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); - case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); - case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); - case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inMessageToken); - os.end(outMessagesToken); - } + void disableProtoLogToLogcat(@NonNull String... groups); } diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java new file mode 100644 index 000000000000..e382ac1513e0 --- /dev/null +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java @@ -0,0 +1,454 @@ +/* + * 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.internal.protolog; + +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.util.Log; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing + * system. Currently this service has the following roles: + * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. + * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog + * clients. This is for two reasons: firstly, because client processes might be frozen so might + * not response to the request to dump their viewer config when the trace is stopped; secondly, + * multiple processes might be running the same code with the same viewer config, this centralized + * service ensures we don't dump the same viewer config multiple times across processes. + * <p> + * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to + * this service on initialization. + * <p> + * This service is intended to run on the system server, such that it never gets frozen. + */ +@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) +public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationService.Stub + implements ProtoLogConfigurationService { + private static final String LOG_TAG = "ProtoLogConfigurationService"; + + private final ProtoLogDataSource mDataSource; + + /** + * Keeps track of how many of each viewer config file is currently registered. + * Use to keep track of which viewer config files are actively being used in tracing and might + * need to be dumped on flush. + */ + private final Map<String, Integer> mConfigFileCounts = new HashMap<>(); + /** + * Keeps track of the viewer config file of each client if available. + */ + private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>(); + + /** + * Keeps track of all the protolog groups that have been registered by clients and are still + * being actively traced. + */ + private final Set<String> mRegisteredGroups = new HashSet<>(); + /** + * Keeps track of all the clients that are actively tracing a given protolog group. + */ + private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>(); + + /** + * Keeps track of whether or not a given group should be logged to logcat. + * True when logging to logcat, false otherwise. + */ + private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>(); + + /** + * Keeps track of all the tracing instance ids that are actively running for ProtoLog. + */ + private final Set<Integer> mRunningInstances = new HashSet<>(); + + private final ViewerConfigFileTracer mViewerConfigFileTracer; + + public ProtoLogConfigurationServiceImpl() { + this(ProtoLogDataSource::new, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { + this(dataSourceBuilder, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ViewerConfigFileTracer tracer) { + this(ProtoLogDataSource::new, tracer); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl( + @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, + @NonNull ViewerConfigFileTracer tracer) { + mDataSource = dataSourceBuilder.build( + this::onTracingInstanceStart, + this::onTracingInstanceFlush, + this::onTracingInstanceStop + ); + + // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be + // receive the lifecycle callbacks of the datasource and write the viewer configs if and + // when required to the datasource. + Producer.init(InitArguments.DEFAULTS); + final var params = new DataSourceParams.Builder() + .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) + .build(); + mDataSource.register(params); + + mViewerConfigFileTracer = tracer; + } + + public static class RegisterClientArgs extends IRegisterClientArgs.Stub { + /** + * The viewer config file to be registered for this client ProtoLog process. + */ + @Nullable + private String mViewerConfigFile = null; + /** + * The list of all groups that this client protolog process supports and might trace. + */ + @NonNull + private String[] mGroups = new String[0]; + /** + * The default logcat status of the ProtoLog client. True is logging to logcat, false + * otherwise. The indices should match the indices in {@link mGroups}. + */ + @NonNull + private boolean[] mLogcatStatus = new boolean[0]; + + public record GroupConfig(@NonNull String group, boolean logToLogcat) {} + + /** + * Specify groups to register with this client that will be used for protologging in this + * process. + * @param groups to register with this client. + * @return self + */ + public RegisterClientArgs setGroups(GroupConfig... groups) { + mGroups = new String[groups.length]; + mLogcatStatus = new boolean[groups.length]; + + for (int i = 0; i < groups.length; i++) { + mGroups[i] = groups[i].group; + mLogcatStatus[i] = groups[i].logToLogcat; + } + + return this; + } + + /** + * Set the viewer config file that the logs in this process are using. + * @param viewerConfigFile The file path of the viewer config. + * @return self + */ + public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { + mViewerConfigFile = viewerConfigFile; + + return this; + } + + @Override + @NonNull + public String[] getGroups() { + return mGroups; + } + + @Override + @NonNull + public boolean[] getGroupsDefaultLogcatStatus() { + return mLogcatStatus; + } + + @Nullable + @Override + public String getViewerConfigFile() { + return mViewerConfigFile; + } + } + + @FunctionalInterface + public interface ViewerConfigFileTracer { + /** + * Write the viewer config data to the trace buffer. + * + * @param dataSource The target datasource to write the viewer config to. + * @param viewerConfigFilePath The path of the viewer config file which contains the data we + * want to write to the trace buffer. + * @throws FileNotFoundException if the viewerConfigFilePath is invalid. + */ + void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); + } + + @Override + public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) + throws RemoteException { + client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); + + final String viewerConfigFile = args.getViewerConfigFile(); + if (viewerConfigFile != null) { + registerViewerConfigFile(client, viewerConfigFile); + } + + registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); + } + + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new ProtoLogCommandHandler(this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + /** + * Get the list of groups clients have registered to the protolog service. + * @return The list of ProtoLog groups registered with this service. + */ + @Override + @NonNull + public String[] getGroups() { + return mRegisteredGroups.toArray(new String[0]); + } + + /** + * Enable logging target groups to logcat. + * @param groups we want to enable logging them to logcat for. + */ + @Override + public void enableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(true, groups); + } + + /** + * Disable logging target groups to logcat. + * @param groups we want to disable from being logged to logcat. + */ + @Override + public void disableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(false, groups); + } + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + @Override + public boolean isLoggingToLogcat(@NonNull String group) { + final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); + + if (isLoggingToLogcat == null) { + throw new RuntimeException( + "Trying to get logcat logging status of non-registered group " + group); + } + + return isLoggingToLogcat; + } + + private void registerViewerConfigFile( + @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { + final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); + mConfigFileCounts.put(viewerConfigFile, count + 1); + mClientConfigFiles.put(client, viewerConfigFile); + } + + private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, + @NonNull boolean[] logcatStatuses) throws RemoteException { + if (groups.length != logcatStatuses.length) { + throw new RuntimeException( + "Expected groups and logcatStatuses to have the same length, " + + "but groups has length " + groups.length + + " and logcatStatuses has length " + logcatStatuses.length); + } + + for (int i = 0; i < groups.length; i++) { + String group = groups[i]; + boolean logcatStatus = logcatStatuses[i]; + + mRegisteredGroups.add(group); + + mGroupToClients.putIfAbsent(group, new HashSet<>()); + mGroupToClients.get(group).add(client); + + if (!mLogGroupToLogcatStatus.containsKey(group)) { + mLogGroupToLogcatStatus.put(group, logcatStatus); + } + + boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); + if (requestedLogToLogcat != logcatStatus) { + client.toggleLogcat(requestedLogToLogcat, new String[] { group }); + } + } + } + + private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { + final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>(); + + for (String group : groups) { + final var clients = mGroupToClients.get(group); + + if (clients == null) { + // No clients associated to this group + Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group + + " with no registered clients."); + continue; + } + + for (IProtoLogClient client : clients) { + clientToGroups.putIfAbsent(client, new HashSet<>()); + clientToGroups.get(client).add(group); + } + } + + for (IProtoLogClient client : clientToGroups.keySet()) { + try { + client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); + } catch (RemoteException e) { + throw new RuntimeException( + "Failed to toggle logcat status for groups on client", e); + } + } + + for (String group : groups) { + mLogGroupToLogcatStatus.put(group, enabled); + } + } + + private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.add(instanceIdx); + } + + private void onTracingInstanceFlush() { + for (String fileName : mConfigFileCounts.keySet()) { + mViewerConfigFileTracer.trace(mDataSource, fileName); + } + } + + private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.remove(instanceIdx); + } + + private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, + @NonNull String viewerConfigFilePath) { + Utils.dumpViewerConfig(dataSource, () -> { + try { + return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "Failed to load viewer config file " + viewerConfigFilePath, e); + } + }); + } + + private void onClientBinderDeath(@NonNull IProtoLogClient client) { + // Dump the tracing config now if no other client is going to dump the same config file. + String configFile = mClientConfigFiles.get(client); + if (configFile != null) { + final var newCount = mConfigFileCounts.get(configFile) - 1; + mConfigFileCounts.put(configFile, newCount); + boolean lastProcessWithViewerConfig = newCount == 0; + if (lastProcessWithViewerConfig) { + mViewerConfigFileTracer.trace(mDataSource, configFile); + } + } + } + + private static void writeViewerConfigGroup( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inGroupToken = pis.start(GROUPS); + final long outGroupToken = os.start(GROUPS); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) ID -> { + int id = pis.readInt(ID); + os.write(ID, id); + } + case (int) NAME -> { + String name = pis.readString(NAME); + os.write(NAME, name); + } + case (int) TAG -> { + String tag = pis.readString(TAG); + os.write(TAG, tag); + } + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inGroupToken); + os.end(outGroupToken); + } + + private static void writeViewerConfigMessage( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inMessageToken = pis.start(MESSAGES); + final long outMessagesToken = os.start(MESSAGES); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) MESSAGE_ID -> os.write(MESSAGE_ID, + pis.readLong(MESSAGE_ID)); + case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); + case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); + case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); + case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inMessageToken); + os.end(outMessagesToken); + } +} 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/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/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index d253736c6db5..c545d73734f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -2007,7 +2007,7 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void selectionChanged(BubbleViewProvider selectedBubble) { // Only need to update the layer view if we're currently expanded for selection changes. - if (mLayerView != null && mLayerView.isExpanded()) { + if (mLayerView != null && isStackExpanded()) { mLayerView.showExpandedView(selectedBubble); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 1367b7e24bc7..1c9c195cf718 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -186,10 +186,6 @@ public class BubbleBarLayerView extends FrameLayout if (expandedView == null) { return; } - if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) { - // Already showing this bubble, skip animating - return; - } if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { removeView(mExpandedView); mExpandedView = null; 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/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index d7bf20130b71..e13e136550ca 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -73,6 +73,7 @@ static void simplifyPaint(int color, Paint* paint) { } paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); + paint->setBlendMode(SkBlendMode::kSrcOver); } class DrawTextFunctor { 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/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/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/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 207c9614e555..855ebe3799b8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -538,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" @@ -548,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 { 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/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/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 70529cc762e0..ee65fbd810ae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -21,6 +21,8 @@ import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.logging.uiEventLogger +import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR import com.android.systemui.SysuiTestCase @@ -28,6 +30,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.coroutines.collectLastValue @@ -35,7 +38,6 @@ import com.android.systemui.dock.dockManager import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -93,6 +95,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { bgScope = applicationCoroutineScope, mainDispatcher = testDispatcher, centralSurfacesOpt = centralSurfacesOptional, + uiEventLogger = uiEventLoggerFake, ) .apply { start() } @@ -119,7 +122,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -140,7 +143,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -161,7 +164,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) @@ -181,7 +184,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) // Scene change will be handled in EditWidgetsActivity not here assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -200,7 +203,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -220,7 +223,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) } @@ -240,7 +243,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -258,7 +261,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) } @@ -276,7 +279,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -298,7 +301,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2) @@ -307,7 +310,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, to = KeyguardState.GLANCEABLE_HUB, - testScope = this + testScope = this, ) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY) @@ -327,7 +330,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) updateDocked(true) @@ -349,7 +352,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) updateDocked(true) @@ -361,7 +364,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.DREAMING, - testScope = this + testScope = this, ) advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY) assertThat(scene).isEqualTo(CommunalScenes.Blank) @@ -511,6 +514,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) + assertThat(uiEventLoggerFake.logs.first().eventId) + .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) } } @@ -526,7 +532,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) 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/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/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 6e01393015ed..08a7c395e57f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -19,11 +19,13 @@ package com.android.systemui.communal import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable 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.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState @@ -84,6 +86,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Background private val bgScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, + private val uiEventLogger: UiEventLogger, ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT @@ -146,7 +149,7 @@ constructor( screenTimeout = systemSettings.getInt( Settings.System.SCREEN_OFF_TIMEOUT, - DEFAULT_SCREEN_TIMEOUT + DEFAULT_SCREEN_TIMEOUT, ) } .launchIn(bgScope) @@ -160,7 +163,7 @@ constructor( combine( communalSceneInteractor.currentScene, // Emit a value on start so the combine starts. - communalInteractor.userActivity.emitOnStart() + communalInteractor.userActivity.emitOnStart(), ) { scene, _ -> // Only timeout if we're on the hub is open. scene == CommunalScenes.Communal @@ -184,6 +187,7 @@ constructor( CommunalScenes.Blank, "dream started after timeout", ) + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) } } } @@ -212,6 +216,7 @@ constructor( newScene = CommunalScenes.Blank, loggingReason = "hub timeout", ) + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) } timeoutJob = null } @@ -219,7 +224,7 @@ constructor( } private suspend fun determineSceneAfterTransition( - lastStartedTransition: TransitionStep, + lastStartedTransition: TransitionStep ): Pair<SceneKey, TransitionKey>? { val to = lastStartedTransition.to val from = lastStartedTransition.from @@ -251,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/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/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/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/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/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/statusbar/core/StatusBarSimpleFragment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt new file mode 100644 index 000000000000..214151383dc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt @@ -0,0 +1,60 @@ +/* + * 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.core + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** 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 + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.statusBarSimpleFragment() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * 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 + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} 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/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/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/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/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/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/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 ac75ef7b4656..8e41d18f0953 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1688,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/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/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 621c090d37b8..48d24f2e14dd 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -179,7 +179,8 @@ public final class MediaProjectionManagerService extends SystemService /** * In order to record the keyguard, the MediaProjection package must be either: * - a holder of RECORD_SENSITIVE_CONTENT permission, or - * - be one of the bugreport whitelisted packages + * - be one of the bugreport allowlisted packages, or + * - hold the OP_PROJECT_MEDIA AppOp. */ private boolean canCaptureKeyguard() { if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { @@ -194,6 +195,14 @@ public final class MediaProjectionManagerService extends SystemService == PackageManager.PERMISSION_GRANTED) { return true; } + boolean operationActive = mAppOps.isOperationActive(AppOpsManager.OP_PROJECT_MEDIA, + mProjectionGrant.uid, + mProjectionGrant.packageName); + if (operationActive) { + // Some tools use media projection by granting the OP_PROJECT_MEDIA app + // op via a shell command. Those tools can be granted keyguard capture + return true; + } return SystemConfig.getInstance().getBugreportWhitelistedPackages() .contains(mProjectionGrant.packageName); } 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/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/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 68026ea9094a..e3d71e4998be 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -24,8 +24,11 @@ import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -78,6 +81,8 @@ import java.util.function.Consumer; public final class RollbackPackageHealthObserver implements PackageHealthObserver { private static final String TAG = "RollbackPackageHealthObserver"; private static final String NAME = "rollback-observer"; + private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName(); + private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -596,12 +601,40 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } }; - final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> { - mHandler.post(() -> onResult.accept(result)); - }); + if (Flags.refactorCrashrecovery()) { + // Define a BroadcastReceiver to handle the result + BroadcastReceiver rollbackReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent result) { + mHandler.post(() -> onResult.accept(result)); + } + }; + + // Register the BroadcastReceiver + mContext.registerReceiver(rollbackReceiver, + new IntentFilter(ACTION_NAME), + Context.RECEIVER_NOT_EXPORTED); + + Intent intentReceiver = new Intent(ACTION_NAME); + intentReceiver.putExtra("rollbackId", rollback.getRollbackId()); + intentReceiver.setPackage(mContext.getPackageName()); - rollbackManager.commitRollback(rollback.getRollbackId(), - Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender()); + PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext, + rollback.getRollbackId(), + intentReceiver, + PendingIntent.FLAG_MUTABLE); + + rollbackManager.commitRollback(rollback.getRollbackId(), + Collections.singletonList(failedPackage), + rollbackPendingIntent.getIntentSender()); + } else { + final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> { + mHandler.post(() -> onResult.accept(result)); + }); + + rollbackManager.commitRollback(rollback.getRollbackId(), + Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender()); + } } /** 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/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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ab459df1cdf6..9fdf088c3d1d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -107,7 +107,7 @@ import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.ProtoLog; -import com.android.internal.protolog.ProtoLogConfigurationService; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; @@ -1097,7 +1097,7 @@ public final class SystemServer implements Dumpable { if (android.tracing.Flags.clientSideProtoLogging()) { t.traceBegin("StartProtoLogConfigurationService"); ServiceManager.addService( - Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService()); + Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationServiceImpl()); t.traceEnd(); } 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/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/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 689b241f0faa..abc9ce3fdc36 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -50,11 +50,12 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; +import android.app.AppOpsManager; import android.app.KeyguardManager; import android.content.Context; -import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ApplicationInfoFlags; @@ -72,6 +73,7 @@ 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.testing.TestableContext; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; @@ -99,13 +101,14 @@ import java.util.concurrent.TimeUnit; /** * Tests for the {@link MediaProjectionManagerService} class. - * + * <p> * Build/Install/Run: * atest FrameworksServicesTests:MediaProjectionManagerServiceTest */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) +@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"}) public class MediaProjectionManagerServiceTest { private static final int UID = 10; private static final String PACKAGE_NAME = "test.package"; @@ -151,7 +154,10 @@ public class MediaProjectionManagerServiceTest { } }; - private Context mContext; + @Rule + public final TestableContext mContext = spy( + new TestableContext(InstrumentationRegistry.getInstrumentation().getContext())); + private MediaProjectionManagerService mService; private OffsettableClock mClock; private ContentRecordingSession mWaitingDisplaySession = @@ -169,6 +175,8 @@ public class MediaProjectionManagerServiceTest { @Mock private KeyguardManager mKeyguardManager; @Mock + AppOpsManager mAppOpsManager; + @Mock private IMediaProjectionWatcherCallback mWatcherCallback; @Mock private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @@ -185,10 +193,9 @@ public class MediaProjectionManagerServiceTest { LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); - mContext = spy(new ContextWrapper( - InstrumentationRegistry.getInstrumentation().getTargetContext())); - doReturn(mPackageManager).when(mContext).getPackageManager(); - doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE)); + mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager); + mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager); + mContext.setMockPackageManager(mPackageManager); mClock = new OffsettableClock.Stopped(); mWaitingDisplaySession.setWaitingForConsent(true); @@ -291,6 +298,27 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } + @SuppressLint("MissingPermission") + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test + public void testCreateProjection_keyguardLocked_AppOpMediaProjection() + throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + doReturn(true).when(mAppOpsManager).isOperationActive(eq(AppOpsManager.OP_PROJECT_MEDIA), + eq(projection.uid), eq(projection.packageName)); + doReturn(true).when(mKeyguardManager).isKeyguardLocked(); + + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + RECORD_SENSITIVE_CONTENT, projection.packageName); + + projection.start(mIMediaProjectionCallback); + projection.notifyVirtualDisplayCreated(10); + + // The projection was started because it was allowed to capture the keyguard. + assertThat(mService.getActiveProjectionInfo()).isNotNull(); + } + @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { 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/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/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index e841d9ea0880..c882b4e569a1 100644 --- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -42,7 +42,7 @@ import android.util.proto.ProtoInputStream; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogDataType; import com.android.internal.protolog.common.LogLevel; @@ -166,7 +166,8 @@ public class PerfettoProtoLogImplTest { return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()); }); }; - sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer); + sProtoLogConfigurationService = + new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer); if (android.tracing.Flags.clientSideProtoLogging()) { sProtoLog = new PerfettoProtoLogImpl( diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java index e1bdd777dc5f..a3d03a8278ed 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java @@ -150,11 +150,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void canRegisterClientWithGroupsOnly() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -165,11 +165,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnlyOnceOnTraceStop() throws RemoteException, InvalidProtocolBufferException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -200,13 +200,13 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnLastClientDisconnected() throws RemoteException, FileNotFoundException { - final ProtoLogConfigurationService.ViewerConfigFileTracer tracer = - Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class); - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer); + final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer = + Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -225,10 +225,10 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendEnableLoggingToLogcatToClient() throws RemoteException { - final var service = new ProtoLogConfigurationService(); + final var service = new ProtoLogConfigurationServiceImpl(); - final var args = new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -242,11 +242,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendDisableLoggingToLogcatToClient() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -260,11 +260,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -277,15 +277,15 @@ public class ProtoLogConfigurationServiceTest { @Test public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP); service.enableProtoLogToLogcat(TEST_GROUP); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); 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() |