diff options
328 files changed, 9417 insertions, 2540 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 3883a9667355..e9a63f74d59f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -11134,6 +11134,7 @@ package android.content { field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims"; field public static final String TELEPHONY_SERVICE = "phone"; field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; + field public static final String TETHERING_SERVICE = "tethering"; field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification"; field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String TV_AD_SERVICE = "tv_ad"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8a5276c1ce08..7483316e94b0 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3800,7 +3800,6 @@ package android.content { field public static final String STATS_MANAGER = "stats"; field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; - field public static final String TETHERING_SERVICE = "tethering"; field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network"; field public static final String TIME_MANAGER_SERVICE = "time_manager"; field public static final String TRANSLATION_MANAGER_SERVICE = "translation"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e2fe5062d356..36ef4f5f06ee 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -478,8 +478,8 @@ package android.app { method public void destroy(); method @NonNull public java.util.Set<java.lang.String> getAdoptedShellPermissions(); method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle); - method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean); - method public void injectInputEventToInputFilter(@NonNull android.view.InputEvent); + method @Deprecated @FlaggedApi("com.android.input.flags.deprecate_uiautomation_input_injection") public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean); + method @Deprecated @FlaggedApi("com.android.input.flags.deprecate_uiautomation_input_injection") public void injectInputEventToInputFilter(@NonNull android.view.InputEvent); method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public void removeOverridePermissionState(int, @NonNull String); method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 8fa2362139a1..ff39329a0d2d 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -114,7 +114,6 @@ interface INotificationManager NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, String conversationId, boolean includeDeleted); void deleteNotificationChannel(String pkg, String channelId); ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId); - ParceledListSlice getOrCreateNotificationChannels(String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded); ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getDeletedChannelCount(String pkg, int uid); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 35308ee43dea..40db6dd1b0ba 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1772,6 +1772,11 @@ public class Notification implements Parcelable */ public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; + /** + * @hide + */ + public static final String EXTRA_SUMMARIZED_CONTENT = "android.summarization"; + @UnsupportedAppUsage private Icon mSmallIcon; @UnsupportedAppUsage diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 1e1ec602d0a2..21dad28560df 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1264,8 +1264,7 @@ public class NotificationManager { mNotificationChannelListCache.query(new NotificationChannelQuery( mContext.getOpPackageName(), mContext.getPackageName(), - mContext.getUserId(), - true))); // create (default channel) if needed + mContext.getUserId()))); } else { INotificationManager service = service(); try { @@ -1293,8 +1292,7 @@ public class NotificationManager { mNotificationChannelListCache.query(new NotificationChannelQuery( mContext.getOpPackageName(), mContext.getPackageName(), - mContext.getUserId(), - true))); // create (default channel) if needed + mContext.getUserId()))); } else { INotificationManager service = service(); try { @@ -1320,8 +1318,7 @@ public class NotificationManager { return mNotificationChannelListCache.query(new NotificationChannelQuery( mContext.getOpPackageName(), mContext.getPackageName(), - mContext.getUserId(), - false)); + mContext.getUserId())); } else { INotificationManager service = service(); try { @@ -1461,8 +1458,8 @@ public class NotificationManager { public List<NotificationChannel> apply(NotificationChannelQuery query) { INotificationManager service = service(); try { - return service.getOrCreateNotificationChannels(query.callingPkg, - query.targetPkg, query.userId, query.createIfNeeded).getList(); + return service.getNotificationChannels(query.callingPkg, + query.targetPkg, query.userId).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1490,8 +1487,7 @@ public class NotificationManager { private record NotificationChannelQuery( String callingPkg, String targetPkg, - int userId, - boolean createIfNeeded) {} + int userId) {} /** * @hide diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 063055eb4917..8021ab4865af 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -18,6 +18,8 @@ package android.app; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.input.flags.Flags.FLAG_DEPRECATE_UIAUTOMATION_INPUT_INJECTION; + import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityService.Callbacks; @@ -26,6 +28,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.MagnificationConfig; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -108,7 +111,10 @@ import java.util.concurrent.TimeoutException; * client should be using a higher-level library or implement high-level functions. * For example, performing a tap on the screen requires construction and injecting * of a touch down and up events which have to be delivered to the system by a - * call to {@link #injectInputEvent(InputEvent, boolean)}. + * call to {@link #injectInputEvent(InputEvent, boolean)}. <strong>Note:</strong> For CTS tests, it + * is preferable to inject input events using uinput (com.android.cts.input.UinputDevice) or hid + * devices (com.android.cts.input.HidDevice). Alternatively, use InjectInputInProcess + * (com.android.cts.input.InjectInputInProcess) for in-process injection. * </p> * <p> * The APIs exposed by this class operate across applications enabling a client @@ -957,9 +963,17 @@ public final class UiAutomation { * <strong>Note:</strong> It is caller's responsibility to recycle the event. * </p> * - * @param event The event to inject. - * @param sync Whether to inject the event synchronously. - * @return Whether event injection succeeded. + * <p> + * <strong>Note:</strong> Avoid this method when injecting input events in CTS tests. Instead + * use uinput (com.android.cts.input.UinputDevice) + * or hid devices (com.android.cts.input.HidDevice), as they provide a more accurate simulation + * of real device behavior. Alternatively, InjectInputInProcess + * (com.android.cts.input.InjectInputProcess) can be used for in-process injection. + * </p> + * + * @param event the event to inject + * @param sync whether to inject the event synchronously + * @return {@code true} if event injection succeeded */ public boolean injectInputEvent(InputEvent event, boolean sync) { return injectInputEvent(event, sync, true /* waitForAnimations */); @@ -972,15 +986,21 @@ public final class UiAutomation { * <strong>Note:</strong> It is caller's responsibility to recycle the event. * </p> * - * @param event The event to inject. - * @param sync Whether to inject the event synchronously. - * @param waitForAnimations Whether to wait for all window container animations and surface - * operations to complete. - * @return Whether event injection succeeded. + * @param event the event to inject + * @param sync whether to inject the event synchronously. + * @param waitForAnimations whether to wait for all window container animations and surface + * operations to complete + * @return {@code true} if event injection succeeded * + * @deprecated for CTS tests prefer inject input events using uinput + * (com.android.cts.input.UinputDevice) or hid devices (com.android.cts.input.HidDevice). + * Alternatively, InjectInputInProcess (com.android.cts.input.InjectInputProcess) can be used + * for in-process injection. * @hide */ @TestApi + @Deprecated // Deprecated for CTS tests + @FlaggedApi(FLAG_DEPRECATE_UIAUTOMATION_INPUT_INJECTION) public boolean injectInputEvent(@NonNull InputEvent event, boolean sync, boolean waitForAnimations) { try { @@ -1003,9 +1023,15 @@ public final class UiAutomation { * Events injected to the input subsystem using the standard {@link #injectInputEvent} method * skip the accessibility input filter to avoid feedback loops. * + * @deprecated for CTS tests prefer inject input events using uinput + * (com.android.cts.input.UinputDevice) or hid devices (com.android.cts.input.HidDevice). + * Alternatively, InjectInputInProcess (com.android.cts.input.InjectInputProcess) can be used + * for in-process injection. * @hide */ @TestApi + @Deprecated + @FlaggedApi(FLAG_DEPRECATE_UIAUTOMATION_INPUT_INJECTION) public void injectInputEventToInputFilter(@NonNull InputEvent event) { try { mUiAutomationConnection.injectInputEventToInputFilter(event); diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index a4a6a55fc9ab..733a348aa825 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -62,13 +62,6 @@ flag { } flag { - name: "modes_ui_test" - namespace: "systemui" - description: "Guards new CTS tests for Modes; dependent on flags modes_api and modes_ui" - bug: "360862012" -} - -flag { name: "modes_hsum" namespace: "systemui" description: "Fixes for modes (and DND/Zen in general) with HSUM or secondary users" diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl index 4598421eb3bc..c3f3b1ced33c 100644 --- a/core/java/android/app/supervision/ISupervisionManager.aidl +++ b/core/java/android/app/supervision/ISupervisionManager.aidl @@ -22,4 +22,5 @@ package android.app.supervision; */ interface ISupervisionManager { boolean isSupervisionEnabledForUser(int userId); + String getActiveSupervisionAppPackage(int userId); } diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index 92241f3634e8..12432ddd0eb9 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -16,6 +16,7 @@ package android.app.supervision; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; @@ -98,4 +99,20 @@ public class SupervisionManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the package name of the app that is acting as the active supervision app or null if + * supervision is disabled. + * + * @hide + */ + @UserHandleAware + @Nullable + public String getActiveSupervisionAppPackage() { + try { + return mService.getActiveSupervisionAppPackage(mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 6da2a073ec19..1cf42820f356 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -18,13 +18,6 @@ flag { } flag { - namespace: "virtual_devices" - name: "media_projection_keyguard_restrictions" - description: "Auto-stop MP when the device locks" - bug: "348335290" -} - -flag { namespace: "virtual_devices" name: "virtual_display_insets" description: "APIs for specifying virtual display insets (via cutout)" diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 8d54673df74c..d811c0791c6c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4964,10 +4964,10 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.TetheringManager} * for managing tethering functions. - * @hide + * * @see android.net.TetheringManager */ - @SystemApi + @SuppressLint("UnflaggedApi") public static final String TETHERING_SERVICE = "tethering"; /** diff --git a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java index de93234445ca..d3fb93588762 100644 --- a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java +++ b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java @@ -19,6 +19,7 @@ package android.hardware.biometrics; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.DevicePolicyManager; +import android.app.supervision.SupervisionManager; import android.content.ComponentName; import android.content.Context; import android.os.Build; @@ -55,27 +56,44 @@ public class ParentalControlsUtilsInternal { return null; } - public static boolean parentConsentRequired(@NonNull Context context, - @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality, + /** @return true if parental consent is required in order for biometric sensors to be used. */ + public static boolean parentConsentRequired( + @NonNull Context context, + @NonNull DevicePolicyManager dpm, + @Nullable SupervisionManager sm, + @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) { if (getTestComponentName(context, userHandle.getIdentifier()) != null) { return true; } - return parentConsentRequired(dpm, modality, userHandle); + return parentConsentRequired(dpm, sm, modality, userHandle); } /** * @return true if parental consent is required in order for biometric sensors to be used. */ - public static boolean parentConsentRequired(@NonNull DevicePolicyManager dpm, - @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) { - final ComponentName cn = getSupervisionComponentName(dpm, userHandle); - if (cn == null) { - return false; + public static boolean parentConsentRequired( + @NonNull DevicePolicyManager dpm, + @Nullable SupervisionManager sm, + @BiometricAuthenticator.Modality int modality, + @NonNull UserHandle userHandle) { + final int keyguardDisabledFeatures; + + if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) { + if (sm != null && !sm.isSupervisionEnabledForUser(userHandle.getIdentifier())) { + return false; + } + // Check for keyguard features disabled by any admin. + keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(/* admin= */ null); + } else { + final ComponentName cn = getSupervisionComponentName(dpm, userHandle); + if (cn == null) { + return false; + } + keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn); } - final int keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn); final boolean dpmFpDisabled = containsFlag(keyguardDisabledFeatures, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); final boolean dpmFaceDisabled = containsFlag(keyguardDisabledFeatures, @@ -97,7 +115,9 @@ public class ParentalControlsUtilsInternal { return consentRequired; } + /** @deprecated Use {@link SupervisionManager} to check for supervision. */ @Nullable + @Deprecated public static ComponentName getSupervisionComponentName(@NonNull DevicePolicyManager dpm, @NonNull UserHandle userHandle) { return dpm.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle); diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index f913fcfd56d4..86b8fad16275 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -161,6 +161,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private final List<UserBatteryConsumer> mUserBatteryConsumers; private final AggregateBatteryConsumer[] mAggregateBatteryConsumers; private final BatteryStatsHistory mBatteryStatsHistory; + private final long mPreferredHistoryDurationMs; private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout; private CursorWindow mBatteryConsumersCursorWindow; @@ -174,6 +175,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah; mDischargeDurationMs = builder.mDischargeDurationMs; mBatteryStatsHistory = builder.mBatteryStatsHistory; + mPreferredHistoryDurationMs = builder.mPreferredHistoryDurationMs; mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs; mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs; mCustomPowerComponentNames = builder.mCustomPowerComponentNames; @@ -402,8 +404,10 @@ public final class BatteryUsageStats implements Parcelable, Closeable { if (source.readBoolean()) { mBatteryStatsHistory = BatteryStatsHistory.createFromBatteryUsageStatsParcel(source); + mPreferredHistoryDurationMs = source.readLong(); } else { mBatteryStatsHistory = null; + mPreferredHistoryDurationMs = 0; } } @@ -428,7 +432,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { if (mBatteryStatsHistory != null) { dest.writeBoolean(true); - mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest); + mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest, mPreferredHistoryDurationMs); } else { dest.writeBoolean(false); } @@ -919,6 +923,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders = new SparseArray<>(); private BatteryStatsHistory mBatteryStatsHistory; + private long mPreferredHistoryDurationMs; public Builder(@NonNull String[] customPowerComponentNames) { this(customPowerComponentNames, false, false, false, 0); @@ -1092,8 +1097,10 @@ public final class BatteryUsageStats implements Parcelable, Closeable { * Sets the parceled recent history. */ @NonNull - public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory) { + public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory, + long preferredHistoryDurationMs) { mBatteryStatsHistory = batteryStatsHistory; + mPreferredHistoryDurationMs = preferredHistoryDurationMs; return this; } diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 6e67578fadc8..5aed39bd8fa6 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -25,6 +25,7 @@ import com.android.internal.os.MonotonicClock; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.concurrent.TimeUnit; /** * Query parameters for the {@link BatteryStatsManager#getBatteryUsageStats()} call. @@ -77,6 +78,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { public static final int FLAG_BATTERY_USAGE_STATS_ACCUMULATED = 0x0080; private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000; + private static final long DEFAULT_PREFERRED_HISTORY_DURATION_MS = TimeUnit.HOURS.toMillis(2); private final int mFlags; @NonNull @@ -89,6 +91,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { private long mMonotonicEndTime; private final double mMinConsumedPowerThreshold; private final @BatteryConsumer.PowerComponentId int[] mPowerComponents; + private final long mPreferredHistoryDurationMs; private BatteryUsageStatsQuery(@NonNull Builder builder) { mFlags = builder.mFlags; @@ -101,6 +104,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { mMonotonicStartTime = builder.mMonotonicStartTime; mMonotonicEndTime = builder.mMonotonicEndTime; mPowerComponents = builder.mPowerComponents; + mPreferredHistoryDurationMs = builder.mPreferredHistoryDurationMs; } @BatteryUsageStatsFlags @@ -197,6 +201,13 @@ public final class BatteryUsageStatsQuery implements Parcelable { return mAggregatedToTimestamp; } + /** + * Returns the preferred duration of battery history (tail) to be included in the query result. + */ + public long getPreferredHistoryDurationMs() { + return mPreferredHistoryDurationMs; + } + @Override public String toString() { return "BatteryUsageStatsQuery{" @@ -209,6 +220,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { + ", mMonotonicEndTime=" + mMonotonicEndTime + ", mMinConsumedPowerThreshold=" + mMinConsumedPowerThreshold + ", mPowerComponents=" + Arrays.toString(mPowerComponents) + + ", mMaxHistoryDurationMs=" + mPreferredHistoryDurationMs + '}'; } @@ -223,6 +235,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { mAggregatedFromTimestamp = in.readLong(); mAggregatedToTimestamp = in.readLong(); mPowerComponents = in.createIntArray(); + mPreferredHistoryDurationMs = in.readLong(); } @Override @@ -237,6 +250,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeLong(mAggregatedFromTimestamp); dest.writeLong(mAggregatedToTimestamp); dest.writeIntArray(mPowerComponents); + dest.writeLong(mPreferredHistoryDurationMs); } @Override @@ -271,6 +285,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { private long mAggregateToTimestamp; private double mMinConsumedPowerThreshold = 0; private @BatteryConsumer.PowerComponentId int[] mPowerComponents; + private long mPreferredHistoryDurationMs = DEFAULT_PREFERRED_HISTORY_DURATION_MS; /** * Builds a read-only BatteryUsageStatsQuery object. @@ -311,6 +326,16 @@ public final class BatteryUsageStatsQuery implements Parcelable { } /** + * Set the preferred amount of battery history to be included in the result, provided + * that `includeBatteryHistory` is also called. The actual amount of history included in + * the result may vary for performance reasons and may exceed the specified preference. + */ + public Builder setPreferredHistoryDurationMs(long preferredHistoryDurationMs) { + mPreferredHistoryDurationMs = preferredHistoryDurationMs; + return this; + } + + /** * Requests that per-process state data be included in the BatteryUsageStats, if * available. Check {@link BatteryUsageStats#isProcessStateDataIncluded()} on the result * to see if the data is available. diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 1e663342522b..877f130a8b5a 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -601,7 +601,7 @@ public final class MessageQueue { /* This is only read/written from the Looper thread. For use with Concurrent MQ */ private int mNextPollTimeoutMillis; private boolean mMessageDirectlyQueued; - private Message nextMessage(boolean peek) { + private Message nextMessage(boolean peek, boolean returnEarliest) { int i = 0; while (true) { @@ -678,7 +678,7 @@ public final class MessageQueue { * If we have a barrier we should return the async node (if it exists and is ready) */ if (msgNode != null && msgNode.isBarrier()) { - if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) { + if (asyncMsgNode != null && (returnEarliest || now >= asyncMsgNode.getWhen())) { found = asyncMsgNode; } else { next = asyncMsgNode; @@ -692,7 +692,7 @@ public final class MessageQueue { earliest = pickEarliestNode(msgNode, asyncMsgNode); if (earliest != null) { - if (now >= earliest.getWhen()) { + if (returnEarliest || now >= earliest.getWhen()) { found = earliest; } else { next = earliest; @@ -797,7 +797,7 @@ public final class MessageQueue { mMessageDirectlyQueued = false; nativePollOnce(ptr, mNextPollTimeoutMillis); - Message msg = nextMessage(false); + Message msg = nextMessage(false, false); if (msg != null) { msg.markInUse(); return msg; @@ -1374,27 +1374,27 @@ public final class MessageQueue { if (now >= msg.when) { // Got a message. mBlocked = false; - if (prevMsg != null) { - prevMsg.next = msg.next; - if (prevMsg.next == null) { - mLast = prevMsg; - } - } else { - mMessages = msg.next; - if (msg.next == null) { - mLast = null; - } - } - msg.next = null; - msg.markInUse(); - if (msg.isAsynchronous()) { - mAsyncMessageCount--; + } + if (prevMsg != null) { + prevMsg.next = msg.next; + if (prevMsg.next == null) { + mLast = prevMsg; } - if (TRACE) { - Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } else { + mMessages = msg.next; + if (msg.next == null) { + mLast = null; } - return msg; } + msg.next = null; + msg.markInUse(); + if (msg.isAsynchronous()) { + mAsyncMessageCount--; + } + if (TRACE) { + Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } + return msg; } } return null; @@ -1411,7 +1411,7 @@ public final class MessageQueue { throwIfNotTest(); Message ret; if (mUseConcurrent) { - ret = nextMessage(true); + ret = nextMessage(true, true); } else { ret = legacyPeekOrPoll(true); } @@ -1429,7 +1429,7 @@ public final class MessageQueue { Message pollForTest() { throwIfNotTest(); if (mUseConcurrent) { - return nextMessage(false); + return nextMessage(false, true); } else { return legacyPeekOrPoll(false); } @@ -1446,7 +1446,7 @@ public final class MessageQueue { throwIfNotTest(); if (mUseConcurrent) { // Call nextMessage to get the stack drained into our priority queues - nextMessage(true); + nextMessage(true, false); Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); MessageNode queueNode = iterateNext(queueIter); diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java index 80da487a1358..7e0995c251b8 100644 --- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java @@ -588,7 +588,7 @@ public final class MessageQueue { private static final AtomicLong mMessagesDelivered = new AtomicLong(); private boolean mMessageDirectlyQueued; - private Message nextMessage(boolean peek) { + private Message nextMessage(boolean peek, boolean returnEarliest) { int i = 0; while (true) { @@ -665,7 +665,7 @@ public final class MessageQueue { * If we have a barrier we should return the async node (if it exists and is ready) */ if (msgNode != null && msgNode.isBarrier()) { - if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) { + if (asyncMsgNode != null && (returnEarliest || now >= asyncMsgNode.getWhen())) { found = asyncMsgNode; } else { next = asyncMsgNode; @@ -679,7 +679,7 @@ public final class MessageQueue { earliest = pickEarliestNode(msgNode, asyncMsgNode); if (earliest != null) { - if (now >= earliest.getWhen()) { + if (returnEarliest || now >= earliest.getWhen()) { found = earliest; } else { next = earliest; @@ -784,7 +784,7 @@ public final class MessageQueue { mMessageDirectlyQueued = false; nativePollOnce(ptr, mNextPollTimeoutMillis); - Message msg = nextMessage(false); + Message msg = nextMessage(false, false); if (msg != null) { msg.markInUse(); return msg; @@ -1089,7 +1089,7 @@ public final class MessageQueue { */ Long peekWhenForTest() { throwIfNotTest(); - Message ret = nextMessage(true); + Message ret = nextMessage(true, true); return ret != null ? ret.when : null; } @@ -1102,7 +1102,7 @@ public final class MessageQueue { @Nullable Message pollForTest() { throwIfNotTest(); - return nextMessage(false); + return nextMessage(false, true); } /** @@ -1116,7 +1116,7 @@ public final class MessageQueue { throwIfNotTest(); // Call nextMessage to get the stack drained into our priority queues - nextMessage(true); + nextMessage(true, false); Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); MessageNode queueNode = iterateNext(queueIter); diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index cde2ba56fcba..132bdd1e56b8 100644 --- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -759,27 +759,27 @@ public final class MessageQueue { if (now >= msg.when) { // Got a message. mBlocked = false; - if (prevMsg != null) { - prevMsg.next = msg.next; - if (prevMsg.next == null) { - mLast = prevMsg; - } - } else { - mMessages = msg.next; - if (msg.next == null) { - mLast = null; - } - } - msg.next = null; - msg.markInUse(); - if (msg.isAsynchronous()) { - mAsyncMessageCount--; + } + if (prevMsg != null) { + prevMsg.next = msg.next; + if (prevMsg.next == null) { + mLast = prevMsg; } - if (TRACE) { - Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } else { + mMessages = msg.next; + if (msg.next == null) { + mLast = null; } - return msg; } + msg.next = null; + msg.markInUse(); + if (msg.isAsynchronous()) { + mAsyncMessageCount--; + } + if (TRACE) { + Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } + return msg; } } return null; diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 2ec5dbc5612a..f58baffb1367 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -1146,14 +1146,6 @@ interface IWindowManager */ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId); - /* - * Notifies about IME insets animation. - * - * @param running Indicates the insets animation state. - * @param animationType Indicates the {@link InsetsController.AnimationType} - */ - oneway void notifyImeInsetsAnimationStateChanged(boolean running, int animationType); - /** * Returns whether the display with {@code displayId} ignores orientation request. */ diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index e097a0764512..c174fbe0bbcd 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -214,14 +214,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * Notifies when the state of running animation is changed. The state is either "running" or * "idle". * - * @param running {@code true} if the given insets types start running - * {@code false} otherwise. - * @param animationType {@link AnimationType} - * @param insetsTypes {@link Type}. + * @param running {@code true} if there is any animation running; {@code false} otherwise. */ - default void notifyAnimationRunningStateChanged(boolean running, - @AnimationType int animationType, @InsetsType int insetsTypes) { - } + default void notifyAnimationRunningStateChanged(boolean running) {} /** @see ViewRootImpl#isHandlingPointerEvent */ default boolean isHandlingPointerEvent() { @@ -749,8 +744,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner( mFrame, mFromState, mToState, RESIZE_INTERPOLATOR, ANIMATION_DURATION_RESIZE, mTypes, InsetsController.this); - mHost.notifyAnimationRunningStateChanged(true, - runner.getAnimationType(), mTypes); + if (mRunningAnimations.isEmpty()) { + mHost.notifyAnimationRunningStateChanged(true); + } mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType())); } }; @@ -1564,7 +1560,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING); - mHost.notifyAnimationRunningStateChanged(true, animationType, types); + if (mRunningAnimations.isEmpty()) { + mHost.notifyAnimationRunningStateChanged(true); + } mRunningAnimations.add(new RunningAnimation(runner, animationType)); if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: " + useInsetsAnimationThread); @@ -1844,8 +1842,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation break; } } - mHost.notifyAnimationRunningStateChanged( - false, control.getAnimationType(), removedTypes); + if (mRunningAnimations.isEmpty()) { + mHost.notifyAnimationRunningStateChanged(false); + } onAnimationStateChanged(removedTypes, false /* running */); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7c5b300e9d24..cd8a85a66c1a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -24,7 +24,6 @@ import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED; import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND; import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID; import static android.os.Trace.TRACE_TAG_VIEW; -import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.util.SequenceUtils.getInitSeq; import static android.util.SequenceUtils.isIncomingSeqStale; import static android.view.Display.DEFAULT_DISPLAY; @@ -923,8 +922,6 @@ public final class ViewRootImpl implements ViewParent, private boolean mInsetsAnimationRunning; - private int mInsetsAnimatingTypes = 0; - private long mPreviousFrameDrawnTime = -1; // The largest view size percentage to the display size. Used on trace to collect metric. private float mLargestChildPercentage = 0.0f; @@ -2523,49 +2520,17 @@ public final class ViewRootImpl implements ViewParent, * Notify the when the running state of a insets animation changed. */ @VisibleForTesting - public void notifyInsetsAnimationRunningStateChanged(boolean running, - @InsetsController.AnimationType int animationType, - @InsetsType int insetsTypes) { - @InsetsType int previousInsetsType = mInsetsAnimatingTypes; - // If improveFillDialogAconfig is disabled, we notify WindowSession of all the updates we - // receive here - boolean notifyWindowSession = !improveFillDialogAconfig(); - if (running) { - mInsetsAnimatingTypes |= insetsTypes; - } else { - mInsetsAnimatingTypes &= ~insetsTypes; - } + public void notifyInsetsAnimationRunningStateChanged(boolean running) { if (sToolkitSetFrameRateReadOnlyFlagValue) { - mInsetsAnimationRunning = running; - // If improveFillDialogAconfig is enabled, we need to confirm other animations aren't - // running to maintain the existing behavior. System server were notified previously - // only when animation started running or stopped when there were no running animations. - if (improveFillDialogAconfig()) { - if ((previousInsetsType == 0 && mInsetsAnimatingTypes != 0) - || (previousInsetsType != 0 && mInsetsAnimatingTypes == 0)) { - notifyWindowSession = true; - } - } - if (notifyWindowSession) { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instant(Trace.TRACE_TAG_VIEW, - TextUtils.formatSimple("notifyInsetsAnimationRunningStateChanged(%s)", - Boolean.toString(running))); - } - try { - mWindowSession.notifyInsetsAnimationRunningStateChanged(mWindow, running); - } catch (RemoteException e) { - } + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant(Trace.TRACE_TAG_VIEW, + TextUtils.formatSimple("notifyInsetsAnimationRunningStateChanged(%s)", + Boolean.toString(running))); } - } - if (improveFillDialogAconfig()) { - // Update WindowManager for ImeAnimation - if ((insetsTypes & WindowInsets.Type.ime()) != 0) { - try { - WindowManagerGlobal.getWindowManagerService() - .notifyImeInsetsAnimationStateChanged(running, animationType); - } catch (RemoteException e) { - } + mInsetsAnimationRunning = running; + try { + mWindowSession.notifyInsetsAnimationRunningStateChanged(mWindow, running); + } catch (RemoteException e) { } } } diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index f1666dbebd7b..889acca4b8b1 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -275,12 +275,9 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { } @Override - public void notifyAnimationRunningStateChanged(boolean running, - @InsetsController.AnimationType int animationType, - @WindowInsets.Type.InsetsType int insetsTypes) { + public void notifyAnimationRunningStateChanged(boolean running) { if (mViewRoot != null) { - mViewRoot.notifyInsetsAnimationRunningStateChanged( - running, animationType, insetsTypes); + mViewRoot.notifyInsetsAnimationRunningStateChanged(running); } } diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 82055afda8c1..d1e3a2d953ef 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -35,10 +35,6 @@ import java.util.function.BooleanSupplier; * windowing features which are aiming for developer preview before their release. It allows * developer option to override the default behavior of these flags. * - * <p> The flags here will be controlled by either {@link - * Settings.Global#DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES} or the {@code - * persyst.wm.debug.desktop_experience_devopts} system property. - * * <p>NOTE: Flags should only be added to this enum when they have received Product and UX * alignment that the feature is ready for developer preview, otherwise just do a flag check. * @@ -85,18 +81,24 @@ public enum DesktopModeFlags { ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false), ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true), ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX( - Flags::enableDesktopWindowingEnterTransitionBugfix, false), + Flags::enableDesktopWindowingEnterTransitionBugfix, true), ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX( - Flags::enableDesktopWindowingExitTransitionsBugfix, false), + Flags::enableDesktopWindowingExitTransitionsBugfix, true), ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX( - Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false), + Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true), ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX( - Flags::enableDesktopAppLaunchTransitionsBugfix, false), + Flags::enableDesktopAppLaunchTransitionsBugfix, true), INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true), + ENABLE_DESKTOP_WINDOWING_HSUM(Flags::enableDesktopWindowingHsum, true), ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true), ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true), - ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true); + ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true), + ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER( + Flags::enableDesktopWallpaperActivityForSystemUser, true), + ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX( + Flags::enableDesktopRecentsTransitionsCornersBugfix, false), + ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true); /** * Flag class, to be used in case the enum cannot be used because the flag is not accessible. @@ -116,7 +118,7 @@ public enum DesktopModeFlags { /** * Determines state of flag based on the actual flag and desktop mode developer option - * or desktop experience developer option overrides. + * overrides. */ public boolean isTrue() { return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index ce0ccd5c6d0d..68b5a261f507 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -112,6 +112,12 @@ public final class WindowContainerTransaction implements Parcelable { mTaskFragmentOrganizer = null; } + /* + * =========================================================================================== + * Window container properties + * =========================================================================================== + */ + /** * Resize a container. */ @@ -170,20 +176,6 @@ public final class WindowContainerTransaction implements Parcelable { } /** - * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task - * has finished the enter animation with the given bounds. - */ - @NonNull - public WindowContainerTransaction scheduleFinishEnterPip( - @NonNull WindowContainerToken container, @NonNull Rect bounds) { - final Change chg = getOrCreateChange(container.asBinder()); - chg.mPinnedBounds = new Rect(bounds); - chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK; - - return this; - } - - /** * Send a SurfaceControl transaction to the server, which the server will apply in sync with * the next bounds change. As this uses deferred transaction and not BLAST it is only * able to sync with a single window, and the first visible window in this hierarchy of type @@ -204,36 +196,6 @@ public final class WindowContainerTransaction implements Parcelable { } /** - * Like {@link #setBoundsChangeTransaction} but instead queues up a setPosition/WindowCrop - * on a container's surface control. This is useful when a boundsChangeTransaction needs to be - * queued up on a Task that won't be organized until the end of this window-container - * transaction. - * - * This requires that, at the end of this transaction, `task` will be organized; otherwise - * the server will throw an IllegalArgumentException. - * - * WARNING: Use this carefully. Whatever is set here should match the expected bounds after - * the transaction completes since it will likely be replaced by it. This call is - * intended to pre-emptively set bounds on a surface in sync with a buffer when - * otherwise the new bounds and the new buffer would update on different frames. - * - * TODO(b/134365562): remove once TaskOrg drives full-screen or BLAST is enabled. - * - * @hide - */ - @NonNull - public WindowContainerTransaction setBoundsChangeTransaction( - @NonNull WindowContainerToken task, @NonNull Rect surfaceBounds) { - Change chg = getOrCreateChange(task.asBinder()); - if (chg.mBoundsChangeSurfaceBounds == null) { - chg.mBoundsChangeSurfaceBounds = new Rect(); - } - chg.mBoundsChangeSurfaceBounds.set(surfaceBounds); - chg.mChangeMask |= Change.CHANGE_BOUNDS_TRANSACTION_RECT; - return this; - } - - /** * Set the windowing mode of children of a given root task, without changing * the windowing mode of the Task itself. This can be used during transitions * for example to make the activity render it's fullscreen configuration @@ -381,22 +343,115 @@ public final class WindowContainerTransaction implements Parcelable { } /** - * Reparents a container into another one. The effect of a {@code null} parent can vary. For - * example, reparenting a stack to {@code null} will reparent it to its display. + * Sets whether a container is being drag-resized. + * When {@code true}, the client will reuse a single (larger) surface size to avoid + * continuous allocations on every size change. * - * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to - * the bottom. + * @param container WindowContainerToken of the task that changed its drag resizing state + * @hide */ @NonNull - public WindowContainerTransaction reparent(@NonNull WindowContainerToken child, - @Nullable WindowContainerToken parent, boolean onTop) { - mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(), - parent == null ? null : parent.asBinder(), - onTop)); + public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container, + boolean dragResizing) { + final Change change = getOrCreateChange(container.asBinder()); + change.mChangeMask |= Change.CHANGE_DRAG_RESIZING; + change.mDragResizing = dragResizing; return this; } /** + * Sets/removes the always on top flag for this {@code windowContainer}. See + * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}. + * Please note that this method is only intended to be used for a + * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}. + * + * <p> + * Setting always on top to {@code True} will also make the {@code windowContainer} to move + * to the top. + * </p> + * <p> + * Setting always on top to {@code False} will make this {@code windowContainer} to move + * below the other always on top sibling containers. + * </p> + * + * @param windowContainer the container which the flag need to be updated for. + * @param alwaysOnTop denotes whether or not always on top flag should be set. + * @hide + */ + @NonNull + public WindowContainerTransaction setAlwaysOnTop( + @NonNull WindowContainerToken windowContainer, boolean alwaysOnTop) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP) + .setContainer(windowContainer.asBinder()) + .setAlwaysOnTop(alwaysOnTop) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Sets/removes the reparent leaf task flag for this {@code windowContainer}. + * When this is set, the server side will try to reparent the leaf task to task display area + * if there is an existing activity in history during the activity launch. This operation only + * support on the organized root task. + * @hide + */ + @NonNull + public WindowContainerTransaction setReparentLeafTaskIfRelaunch( + @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH) + .setContainer(windowContainer.asBinder()) + .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Defers client-facing configuration changes for activities in `container` until the end of + * the transition animation. The configuration will still be applied to the WMCore hierarchy + * at the normal time (beginning); so, special consideration must be made for this in the + * animation. + * + * @param container WindowContainerToken who's children should defer config notification. + * @hide + */ + @NonNull + public WindowContainerTransaction deferConfigToTransitionEnd( + @NonNull WindowContainerToken container) { + final Change change = getOrCreateChange(container.asBinder()); + change.mConfigAtTransitionEnd = true; + return this; + } + + /** + * Sets the task as trimmable or not. This can be used to prevent the task from being trimmed by + * recents. This attribute is set to true on task creation by default. + * + * @param isTrimmableFromRecents When {@code true}, task is set as trimmable from recents. + * @hide + */ + @NonNull + public WindowContainerTransaction setTaskTrimmableFromRecents( + @NonNull WindowContainerToken container, + boolean isTrimmableFromRecents) { + mHierarchyOps.add( + HierarchyOp.createForSetTaskTrimmableFromRecents(container.asBinder(), + isTrimmableFromRecents)); + return this; + } + + /* + * =========================================================================================== + * Hierarchy updates (create/destroy/reorder/reparent containers) + * =========================================================================================== + */ + + /** * Reorders a container within its parent. * * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to @@ -425,6 +480,22 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Reparents a container into another one. The effect of a {@code null} parent can vary. For + * example, reparenting a stack to {@code null} will reparent it to its display. + * + * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to + * the bottom. + */ + @NonNull + public WindowContainerTransaction reparent(@NonNull WindowContainerToken child, + @Nullable WindowContainerToken parent, boolean onTop) { + mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(), + parent == null ? null : parent.asBinder(), + onTop)); + return this; + } + + /** * Reparent's all children tasks or the top task of {@param currentParent} in the specified * {@param windowingMode} and {@param activityType} to {@param newParent} in their current * z-order. @@ -478,6 +549,116 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Finds and removes a task and its children using its container token. The task is removed + * from recents. + * + * If the task is a root task, its leaves are removed but the root task is not. Use + * {@link #removeRootTask(WindowContainerToken)} to remove the root task. + * + * @param containerToken ContainerToken of Task to be removed + */ + @NonNull + public WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) { + mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder())); + return this; + } + + /** + * Finds and removes a root task created by an organizer and its leaves using its container + * token. + * + * @param containerToken ContainerToken of the root task to be removed + * @hide + */ + @NonNull + public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) { + mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder())); + return this; + } + + /** + * If `container` was brought to front as a transient-launch (eg. recents), this will reorder + * the container back to where it was prior to the transient-launch. This way if a transient + * launch is "aborted", the z-ordering of containers in WM should be restored to before the + * launch. + * @hide + */ + @NonNull + public WindowContainerTransaction restoreTransientOrder( + @NonNull WindowContainerToken container) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER) + .setContainer(container.asBinder()) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Restore the back navigation target from visible to invisible for canceling gesture animation. + * @hide + */ + @NonNull + public WindowContainerTransaction restoreBackNavi() { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /* + * =========================================================================================== + * Activity launch + * =========================================================================================== + */ + + /** + * Starts a task by id. The task is expected to already exist (eg. as a recent task). + * @param taskId Id of task to start. + * @param options bundle containing ActivityOptions for the task's top activity. + * @hide + */ + @NonNull + public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) { + mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options)); + return this; + } + + /** + * Sends a pending intent in sync. + * @param sender The PendingIntent sender. + * @param intent The fillIn intent to patch over the sender's base intent. + * @param options bundle containing ActivityOptions for the task's top activity. + * @hide + */ + @NonNull + public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender, + @Nullable Intent intent, @Nullable Bundle options) { + mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT) + .setLaunchOptions(options) + .setPendingIntent(sender) + .setActivityIntent(intent) + .build()); + return this; + } + + /** + * Starts activity(s) from a shortcut. + * @param callingPackage The package launching the shortcut. + * @param shortcutInfo Information about the shortcut to start + * @param options bundle containing ActivityOptions for the task's top activity. + * @hide + */ + @NonNull + public WindowContainerTransaction startShortcut(@NonNull String callingPackage, + @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) { + mHierarchyOps.add(HierarchyOp.createForStartShortcut( + callingPackage, shortcutInfo, options)); + return this; + } + + /** * Sets whether a container should be the launch root for the specified windowing mode and * activity type. This currently only applies to Task containers created by organizer. */ @@ -491,6 +672,12 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + /* + * =========================================================================================== + * Multitasking + * =========================================================================================== + */ + /** * Sets two containers adjacent to each other. Containers below two visible adjacent roots will * be made invisible. This currently only applies to TaskFragment containers created by @@ -599,93 +786,162 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + /* + * =========================================================================================== + * PIP + * =========================================================================================== + */ + /** - * Starts a task by id. The task is expected to already exist (eg. as a recent task). - * @param taskId Id of task to start. - * @param options bundle containing ActivityOptions for the task's top activity. + * Moves the PiP activity of a parent task to a pinned root task. + * @param parentToken the parent task of the PiP activity + * @param bounds the entry bounds * @hide */ @NonNull - public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) { - mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options)); + public WindowContainerTransaction movePipActivityToPinnedRootTask( + @NonNull WindowContainerToken parentToken, @NonNull Rect bounds) { + mHierarchyOps.add(new HierarchyOp + .Builder(HierarchyOp.HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK) + .setContainer(parentToken.asBinder()) + .setBounds(bounds) + .build()); return this; } /** - * Finds and removes a task and its children using its container token. The task is removed - * from recents. - * - * If the task is a root task, its leaves are removed but the root task is not. Use - * {@link #removeRootTask(WindowContainerToken)} to remove the root task. - * - * @param containerToken ContainerToken of Task to be removed + * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task + * has finished the enter animation with the given bounds. */ @NonNull - public WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) { - mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder())); + public WindowContainerTransaction scheduleFinishEnterPip( + @NonNull WindowContainerToken container, @NonNull Rect bounds) { + final Change chg = getOrCreateChange(container.asBinder()); + chg.mPinnedBounds = new Rect(bounds); + chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK; + return this; } + /* + * =========================================================================================== + * Insets + * =========================================================================================== + */ + /** - * Finds and removes a root task created by an organizer and its leaves using its container - * token. + * Adds a given {@code Rect} as an insets source frame on the {@code receiver}. * - * @param containerToken ContainerToken of the root task to be removed + * @param receiver The window container that the insets source is added to. + * @param owner The owner of the insets source. An insets source can only be modified by its + * owner. + * @param index An owner might add multiple insets sources with the same type. + * This identifies them. + * @param type The {@link InsetsType} of the insets source. + * @param frame The rectangle area of the insets source. + * @param boundingRects The bounding rects within this inset, relative to the |frame|. * @hide */ @NonNull - public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) { - mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder())); + public WindowContainerTransaction addInsetsSource( + @NonNull WindowContainerToken receiver, + @Nullable IBinder owner, int index, @InsetsType int type, @Nullable Rect frame, + @Nullable Rect[] boundingRects, @InsetsSource.Flags int flags) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER) + .setContainer(receiver.asBinder()) + .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type) + .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE) + .setArbitraryRectangle(frame) + .setBoundingRects(boundingRects) + .setFlags(flags)) + .setInsetsFrameOwner(owner) + .build(); + mHierarchyOps.add(hierarchyOp); return this; } /** - * Sets whether a container is being drag-resized. - * When {@code true}, the client will reuse a single (larger) surface size to avoid - * continuous allocations on every size change. + * Removes the insets source from the {@code receiver}. * - * @param container WindowContainerToken of the task that changed its drag resizing state + * @param receiver The window container that the insets source was added to. + * @param owner The owner of the insets source. An insets source can only be modified by its + * owner. + * @param index An owner might add multiple insets sources with the same type. + * This identifies them. + * @param type The {@link InsetsType} of the insets source. * @hide */ @NonNull - public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container, - boolean dragResizing) { - final Change change = getOrCreateChange(container.asBinder()); - change.mChangeMask |= Change.CHANGE_DRAG_RESIZING; - change.mDragResizing = dragResizing; + public WindowContainerTransaction removeInsetsSource(@NonNull WindowContainerToken receiver, + @Nullable IBinder owner, int index, @InsetsType int type) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER) + .setContainer(receiver.asBinder()) + .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)) + .setInsetsFrameOwner(owner) + .build(); + mHierarchyOps.add(hierarchyOp); return this; } + /* + * =========================================================================================== + * Keyguard + * =========================================================================================== + */ + /** - * Sends a pending intent in sync. - * @param sender The PendingIntent sender. - * @param intent The fillIn intent to patch over the sender's base intent. - * @param options bundle containing ActivityOptions for the task's top activity. + * Adds a {@link KeyguardState} to apply to the given displays. + * * @hide */ @NonNull - public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender, - @Nullable Intent intent, @Nullable Bundle options) { - mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT) - .setLaunchOptions(options) - .setPendingIntent(sender) - .setActivityIntent(intent) - .build()); + public WindowContainerTransaction addKeyguardState(@NonNull KeyguardState keyguardState) { + Objects.requireNonNull(keyguardState); + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE) + .setKeyguardState(keyguardState) + .build(); + mHierarchyOps.add(hierarchyOp); return this; } + /* + * =========================================================================================== + * Task fragments + * =========================================================================================== + */ + /** - * Starts activity(s) from a shortcut. - * @param callingPackage The package launching the shortcut. - * @param shortcutInfo Information about the shortcut to start - * @param options bundle containing ActivityOptions for the task's top activity. + * Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}. + * When this is set, the server side will not check for the permission of + * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only + * contains operations that are allowed for this organizer, such as modifying TaskFragments that + * are organized by this organizer. * @hide */ @NonNull - public WindowContainerTransaction startShortcut(@NonNull String callingPackage, - @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) { - mHierarchyOps.add(HierarchyOp.createForStartShortcut( - callingPackage, shortcutInfo, options)); + public WindowContainerTransaction setTaskFragmentOrganizer( + @NonNull ITaskFragmentOrganizer organizer) { + mTaskFragmentOrganizer = organizer; + return this; + } + + /** + * When this {@link WindowContainerTransaction} failed to finish on the server side, it will + * trigger callback with this {@param errorCallbackToken}. + * @param errorCallbackToken client provided token that will be passed back as parameter in + * the callback if there is an error on the server side. + * @see ITaskFragmentOrganizer#onTaskFragmentError + */ + @NonNull + public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) { + if (mErrorCallbackToken != null) { + throw new IllegalStateException("Can't set multiple error token for one transaction."); + } + mErrorCallbackToken = errorCallbackToken; return this; } @@ -793,93 +1049,6 @@ public final class WindowContainerTransaction implements Parcelable { } /** - * If `container` was brought to front as a transient-launch (eg. recents), this will reorder - * the container back to where it was prior to the transient-launch. This way if a transient - * launch is "aborted", the z-ordering of containers in WM should be restored to before the - * launch. - * @hide - */ - @NonNull - public WindowContainerTransaction restoreTransientOrder( - @NonNull WindowContainerToken container) { - final HierarchyOp hierarchyOp = - new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER) - .setContainer(container.asBinder()) - .build(); - mHierarchyOps.add(hierarchyOp); - return this; - } - - /** - * Restore the back navigation target from visible to invisible for canceling gesture animation. - * @hide - */ - @NonNull - public WindowContainerTransaction restoreBackNavi() { - final HierarchyOp hierarchyOp = - new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION) - .build(); - mHierarchyOps.add(hierarchyOp); - return this; - } - - /** - * Adds a given {@code Rect} as an insets source frame on the {@code receiver}. - * - * @param receiver The window container that the insets source is added to. - * @param owner The owner of the insets source. An insets source can only be modified by its - * owner. - * @param index An owner might add multiple insets sources with the same type. - * This identifies them. - * @param type The {@link InsetsType} of the insets source. - * @param frame The rectangle area of the insets source. - * @param boundingRects The bounding rects within this inset, relative to the |frame|. - * @hide - */ - @NonNull - public WindowContainerTransaction addInsetsSource( - @NonNull WindowContainerToken receiver, - @Nullable IBinder owner, int index, @InsetsType int type, @Nullable Rect frame, - @Nullable Rect[] boundingRects, @InsetsSource.Flags int flags) { - final HierarchyOp hierarchyOp = - new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER) - .setContainer(receiver.asBinder()) - .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type) - .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE) - .setArbitraryRectangle(frame) - .setBoundingRects(boundingRects) - .setFlags(flags)) - .setInsetsFrameOwner(owner) - .build(); - mHierarchyOps.add(hierarchyOp); - return this; - } - - /** - * Removes the insets source from the {@code receiver}. - * - * @param receiver The window container that the insets source was added to. - * @param owner The owner of the insets source. An insets source can only be modified by its - * owner. - * @param index An owner might add multiple insets sources with the same type. - * This identifies them. - * @param type The {@link InsetsType} of the insets source. - * @hide - */ - @NonNull - public WindowContainerTransaction removeInsetsSource(@NonNull WindowContainerToken receiver, - @Nullable IBinder owner, int index, @InsetsType int type) { - final HierarchyOp hierarchyOp = - new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER) - .setContainer(receiver.asBinder()) - .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)) - .setInsetsFrameOwner(owner) - .build(); - mHierarchyOps.add(hierarchyOp); - return this; - } - - /** * Requests focus on the top running Activity in the given TaskFragment. This will only take * effect if there is no focus, or if the current focus is in the same Task as the requested * TaskFragment. @@ -961,157 +1130,6 @@ public final class WindowContainerTransaction implements Parcelable { } /** - * Adds a {@link KeyguardState} to apply to the given displays. - * - * @hide - */ - @NonNull - public WindowContainerTransaction addKeyguardState(@NonNull KeyguardState keyguardState) { - Objects.requireNonNull(keyguardState); - final HierarchyOp hierarchyOp = - new HierarchyOp.Builder( - HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE) - .setKeyguardState(keyguardState) - .build(); - mHierarchyOps.add(hierarchyOp); - return this; - } - - /** - * Sets/removes the always on top flag for this {@code windowContainer}. See - * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}. - * Please note that this method is only intended to be used for a - * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}. - * - * <p> - * Setting always on top to {@code True} will also make the {@code windowContainer} to move - * to the top. - * </p> - * <p> - * Setting always on top to {@code False} will make this {@code windowContainer} to move - * below the other always on top sibling containers. - * </p> - * - * @param windowContainer the container which the flag need to be updated for. - * @param alwaysOnTop denotes whether or not always on top flag should be set. - * @hide - */ - @NonNull - public WindowContainerTransaction setAlwaysOnTop( - @NonNull WindowContainerToken windowContainer, boolean alwaysOnTop) { - final HierarchyOp hierarchyOp = - new HierarchyOp.Builder( - HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP) - .setContainer(windowContainer.asBinder()) - .setAlwaysOnTop(alwaysOnTop) - .build(); - mHierarchyOps.add(hierarchyOp); - return this; - } - - /** - * When this {@link WindowContainerTransaction} failed to finish on the server side, it will - * trigger callback with this {@param errorCallbackToken}. - * @param errorCallbackToken client provided token that will be passed back as parameter in - * the callback if there is an error on the server side. - * @see ITaskFragmentOrganizer#onTaskFragmentError - */ - @NonNull - public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) { - if (mErrorCallbackToken != null) { - throw new IllegalStateException("Can't set multiple error token for one transaction."); - } - mErrorCallbackToken = errorCallbackToken; - return this; - } - - /** - * Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}. - * When this is set, the server side will not check for the permission of - * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only - * contains operations that are allowed for this organizer, such as modifying TaskFragments that - * are organized by this organizer. - * @hide - */ - @NonNull - public WindowContainerTransaction setTaskFragmentOrganizer( - @NonNull ITaskFragmentOrganizer organizer) { - mTaskFragmentOrganizer = organizer; - return this; - } - - /** - * Sets/removes the reparent leaf task flag for this {@code windowContainer}. - * When this is set, the server side will try to reparent the leaf task to task display area - * if there is an existing activity in history during the activity launch. This operation only - * support on the organized root task. - * @hide - */ - @NonNull - public WindowContainerTransaction setReparentLeafTaskIfRelaunch( - @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) { - final HierarchyOp hierarchyOp = - new HierarchyOp.Builder( - HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH) - .setContainer(windowContainer.asBinder()) - .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch) - .build(); - mHierarchyOps.add(hierarchyOp); - return this; - } - - /** - * Moves the PiP activity of a parent task to a pinned root task. - * @param parentToken the parent task of the PiP activity - * @param bounds the entry bounds - * @hide - */ - @NonNull - public WindowContainerTransaction movePipActivityToPinnedRootTask( - @NonNull WindowContainerToken parentToken, @NonNull Rect bounds) { - mHierarchyOps.add(new HierarchyOp - .Builder(HierarchyOp.HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK) - .setContainer(parentToken.asBinder()) - .setBounds(bounds) - .build()); - return this; - } - - /** - * Defers client-facing configuration changes for activities in `container` until the end of - * the transition animation. The configuration will still be applied to the WMCore hierarchy - * at the normal time (beginning); so, special consideration must be made for this in the - * animation. - * - * @param container WindowContainerToken who's children should defer config notification. - * @hide - */ - @NonNull - public WindowContainerTransaction deferConfigToTransitionEnd( - @NonNull WindowContainerToken container) { - final Change change = getOrCreateChange(container.asBinder()); - change.mConfigAtTransitionEnd = true; - return this; - } - - /** - * Sets the task as trimmable or not. This can be used to prevent the task from being trimmed by - * recents. This attribute is set to true on task creation by default. - * - * @param isTrimmableFromRecents When {@code true}, task is set as trimmable from recents. - * @hide - */ - @NonNull - public WindowContainerTransaction setTaskTrimmableFromRecents( - @NonNull WindowContainerToken container, - boolean isTrimmableFromRecents) { - mHierarchyOps.add( - HierarchyOp.createForSetTaskTrimmableFromRecents(container.asBinder(), - isTrimmableFromRecents)); - return this; - } - - /** * Merges another WCT into this one. * @param transfer When true, this will transfer everything from other potentially leaving * other in an unusable state. When false, other is left alone, but @@ -1206,7 +1224,7 @@ public final class WindowContainerTransaction implements Parcelable { @NonNull public static final Creator<WindowContainerTransaction> CREATOR = - new Creator<WindowContainerTransaction>() { + new Creator<>() { @Override public WindowContainerTransaction createFromParcel(@NonNull Parcel in) { return new WindowContainerTransaction(in); @@ -1227,19 +1245,17 @@ public final class WindowContainerTransaction implements Parcelable { public static final int CHANGE_BOUNDS_TRANSACTION = 1 << 1; public static final int CHANGE_PIP_CALLBACK = 1 << 2; public static final int CHANGE_HIDDEN = 1 << 3; - public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4; - public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5; - public static final int CHANGE_FORCE_NO_PIP = 1 << 6; - public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7; - public static final int CHANGE_DRAG_RESIZING = 1 << 8; - public static final int CHANGE_RELATIVE_BOUNDS = 1 << 9; + public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 4; + public static final int CHANGE_FORCE_NO_PIP = 1 << 5; + public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 6; + public static final int CHANGE_DRAG_RESIZING = 1 << 7; + public static final int CHANGE_RELATIVE_BOUNDS = 1 << 8; @IntDef(flag = true, prefix = { "CHANGE_" }, value = { CHANGE_FOCUSABLE, CHANGE_BOUNDS_TRANSACTION, CHANGE_PIP_CALLBACK, CHANGE_HIDDEN, - CHANGE_BOUNDS_TRANSACTION_RECT, CHANGE_IGNORE_ORIENTATION_REQUEST, CHANGE_FORCE_NO_PIP, CHANGE_FORCE_TRANSLUCENT, @@ -1262,7 +1278,6 @@ public final class WindowContainerTransaction implements Parcelable { private Rect mPinnedBounds = null; private SurfaceControl.Transaction mBoundsChangeTransaction = null; - private Rect mBoundsChangeSurfaceBounds = null; @Nullable private Rect mRelativeBounds = null; private boolean mConfigAtTransitionEnd = false; @@ -1290,10 +1305,6 @@ public final class WindowContainerTransaction implements Parcelable { mBoundsChangeTransaction = SurfaceControl.Transaction.CREATOR.createFromParcel(in); } - if ((mChangeMask & Change.CHANGE_BOUNDS_TRANSACTION_RECT) != 0) { - mBoundsChangeSurfaceBounds = new Rect(); - mBoundsChangeSurfaceBounds.readFromParcel(in); - } if ((mChangeMask & Change.CHANGE_RELATIVE_BOUNDS) != 0) { mRelativeBounds = new Rect(); mRelativeBounds.readFromParcel(in); @@ -1342,10 +1353,6 @@ public final class WindowContainerTransaction implements Parcelable { if (other.mWindowingMode >= WINDOWING_MODE_UNDEFINED) { mWindowingMode = other.mWindowingMode; } - if (other.mBoundsChangeSurfaceBounds != null) { - mBoundsChangeSurfaceBounds = transfer ? other.mBoundsChangeSurfaceBounds - : new Rect(other.mBoundsChangeSurfaceBounds); - } if (other.mRelativeBounds != null) { mRelativeBounds = transfer ? other.mRelativeBounds @@ -1446,11 +1453,6 @@ public final class WindowContainerTransaction implements Parcelable { } @Nullable - public Rect getBoundsChangeSurfaceBounds() { - return mBoundsChangeSurfaceBounds; - } - - @Nullable public Rect getRelativeBounds() { return mRelativeBounds; } @@ -1529,9 +1531,6 @@ public final class WindowContainerTransaction implements Parcelable { if (mBoundsChangeTransaction != null) { mBoundsChangeTransaction.writeToParcel(dest, flags); } - if (mBoundsChangeSurfaceBounds != null) { - mBoundsChangeSurfaceBounds.writeToParcel(dest, flags); - } if (mRelativeBounds != null) { mRelativeBounds.writeToParcel(dest, flags); } diff --git a/core/java/android/window/flags/device_state_auto_rotate_setting.aconfig b/core/java/android/window/flags/device_state_auto_rotate_setting.aconfig new file mode 100644 index 000000000000..bb66989b9946 --- /dev/null +++ b/core/java/android/window/flags/device_state_auto_rotate_setting.aconfig @@ -0,0 +1,22 @@ +package: "com.android.window.flags" +container: "system" + +flag { + name: "enable_device_state_auto_rotate_setting_logging" + namespace: "windowing_frontend" + description: "Enable device state auto rotate setting logging" + bug: "391147112" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_device_state_auto_rotate_setting_refactor" + namespace: "windowing_frontend" + description: "Enable refactored device state auto rotate setting logic" + bug: "350946537" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index e35c3b80a58b..222088e8a8b9 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -583,3 +583,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_start_launch_transition_from_taskbar_bugfix" + namespace: "lse_desktop_experience" + description: "Enables starting a launch transition directly from the taskbar if desktop tasks are visible." + bug: "361366053" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index 4b5adfcc2c9b..36219812c002 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -81,3 +81,10 @@ flag { description: "Strict mode violation triggered by grace period usage" bug: "384807495" } + +flag { + name: "bal_clear_allowlist_duration" + namespace: "responsible_apis" + description: "Clear the allowlist duration when clearAllowBgActivityStarts is called" + bug: "322159724" +} diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index dc440e36ca0d..f49c5f1c2b0f 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -84,7 +84,7 @@ public class BatteryStatsHistory { private static final String TAG = "BatteryStatsHistory"; // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - private static final int VERSION = 211; + private static final int VERSION = 212; private static final String HISTORY_DIR = "battery-history"; private static final String FILE_SUFFIX = ".bh"; @@ -211,6 +211,8 @@ public class BatteryStatsHistory { private final MonotonicClock mMonotonicClock; // Monotonic time when we started writing to the history buffer private long mHistoryBufferStartTime; + // Monotonic time when the last event was written to the history buffer + private long mHistoryMonotonicEndTime; // Monotonically increasing size of written history private long mMonotonicHistorySize; private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>(); @@ -423,13 +425,22 @@ public class BatteryStatsHistory { return file; } - void writeToParcel(Parcel out, boolean useBlobs) { + void writeToParcel(Parcel out, boolean useBlobs, + long preferredEarliestIncludedTimestampMs) { Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.writeToParcel"); lock(); try { final long start = SystemClock.uptimeMillis(); - out.writeInt(mHistoryFiles.size() - 1); for (int i = 0; i < mHistoryFiles.size() - 1; i++) { + long monotonicEndTime = Long.MAX_VALUE; + if (i < mHistoryFiles.size() - 1) { + monotonicEndTime = mHistoryFiles.get(i + 1).monotonicTimeMs; + } + + if (monotonicEndTime < preferredEarliestIncludedTimestampMs) { + continue; + } + AtomicFile file = mHistoryFiles.get(i).atomicFile; byte[] raw = new byte[0]; try { @@ -437,6 +448,8 @@ public class BatteryStatsHistory { } catch (Exception e) { Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e); } + + out.writeBoolean(true); if (useBlobs) { out.writeBlob(raw); } else { @@ -444,6 +457,7 @@ public class BatteryStatsHistory { out.writeByteArray(raw); } } + out.writeBoolean(false); if (DEBUG) { Slog.d(TAG, "writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start)); @@ -634,6 +648,7 @@ public class BatteryStatsHistory { mWritableHistory = writableHistory; if (mWritableHistory != null) { mMutable = false; + mHistoryMonotonicEndTime = mWritableHistory.mHistoryMonotonicEndTime; } if (historyBuffer != null) { @@ -937,6 +952,8 @@ public class BatteryStatsHistory { } // skip monotonic time field. p.readLong(); + // skip monotonic end time field + p.readLong(); // skip monotonic size field p.readLong(); @@ -996,6 +1013,8 @@ public class BatteryStatsHistory { } // skip monotonic time field. out.readLong(); + // skip monotonic end time field + out.readLong(); // skip monotonic size field out.readLong(); return true; @@ -1024,6 +1043,7 @@ public class BatteryStatsHistory { p.setDataPosition(0); p.readInt(); // Skip the version field long monotonicTime = p.readLong(); + p.readLong(); // Skip monotonic end time field p.readLong(); // Skip monotonic size field p.setDataPosition(pos); return monotonicTime; @@ -1086,7 +1106,10 @@ public class BatteryStatsHistory { public void writeToParcel(Parcel out) { synchronized (this) { writeHistoryBuffer(out); - writeToParcel(out, false /* useBlobs */); + /* useBlobs */ + if (mHistoryDir != null) { + mHistoryDir.writeToParcel(out, false /* useBlobs */, 0); + } } } @@ -1096,16 +1119,13 @@ public class BatteryStatsHistory { * * @param out the output parcel */ - public void writeToBatteryUsageStatsParcel(Parcel out) { + public void writeToBatteryUsageStatsParcel(Parcel out, long preferredHistoryDurationMs) { synchronized (this) { out.writeBlob(mHistoryBuffer.marshall()); - writeToParcel(out, true /* useBlobs */); - } - } - - private void writeToParcel(Parcel out, boolean useBlobs) { - if (mHistoryDir != null) { - mHistoryDir.writeToParcel(out, useBlobs); + if (mHistoryDir != null) { + mHistoryDir.writeToParcel(out, true /* useBlobs */, + mHistoryMonotonicEndTime - preferredHistoryDurationMs); + } } } @@ -1166,8 +1186,7 @@ public class BatteryStatsHistory { private void readFromParcel(Parcel in, boolean useBlobs) { final long start = SystemClock.uptimeMillis(); mHistoryParcels = new ArrayList<>(); - final int count = in.readInt(); - for (int i = 0; i < count; i++) { + while (in.readBoolean()) { byte[] temp = useBlobs ? in.readBlob() : in.createByteArray(); if (temp == null || temp.length == 0) { continue; @@ -2081,6 +2100,8 @@ public class BatteryStatsHistory { */ @GuardedBy("this") private void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) { + mHistoryMonotonicEndTime = cur.time; + if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) { dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS); cur.writeToParcel(dest, 0); @@ -2396,6 +2417,7 @@ public class BatteryStatsHistory { } mHistoryBufferStartTime = in.readLong(); + mHistoryMonotonicEndTime = in.readLong(); mMonotonicHistorySize = in.readLong(); mHistoryBuffer.setDataSize(0); @@ -2424,6 +2446,7 @@ public class BatteryStatsHistory { private void writeHistoryBuffer(Parcel out) { out.writeInt(BatteryStatsHistory.VERSION); out.writeLong(mHistoryBufferStartTime); + out.writeLong(mHistoryMonotonicEndTime); out.writeLong(mMonotonicHistorySize); out.writeInt(mHistoryBuffer.dataSize()); if (DEBUG) { diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index b3ab5d3cd258..04ce9bcd7afd 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -105,6 +105,7 @@ public class ConversationLayout extends FrameLayout private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>(); private Person mUser; private CharSequence mNameReplacement; + private CharSequence mSummarizedContent; private boolean mIsCollapsed; private ImageResolver mImageResolver; private CachingIconView mConversationIconView; @@ -397,7 +398,7 @@ public class ConversationLayout extends FrameLayout * * @param isCollapsed is it collapsed */ - @RemotableViewMethod + @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync") public void setIsCollapsed(boolean isCollapsed) { mIsCollapsed = isCollapsed; mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE); @@ -406,6 +407,15 @@ public class ConversationLayout extends FrameLayout } /** + * setDataAsync needs to do different stuff for the collapsed vs expanded view, so store the + * collapsed state early. + */ + public Runnable setIsCollapsedAsync(boolean isCollapsed) { + mIsCollapsed = isCollapsed; + return () -> setIsCollapsed(isCollapsed); + } + + /** * Set conversation data * * @param extras Bundle contains conversation data @@ -439,8 +449,16 @@ public class ConversationLayout extends FrameLayout extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false); int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); - final List<MessagingMessage> newMessagingMessages = - createMessages(newMessages, /* isHistoric= */false, usePrecomputedText); + List<MessagingMessage> newMessagingMessages; + mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT); + if (mSummarizedContent != null && mIsCollapsed) { + Notification.MessagingStyle.Message summary = + new Notification.MessagingStyle.Message(mSummarizedContent, 0, ""); + newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText); + } else { + newMessagingMessages = + createMessages(newMessages, /* isHistoric= */false, usePrecomputedText); + } final List<MessagingMessage> newHistoricMessagingMessages = createMessages(newHistoricMessages, /* isHistoric= */true, usePrecomputedText); @@ -463,7 +481,7 @@ public class ConversationLayout extends FrameLayout return new MessagingData(user, showSpinner, unreadCount, newHistoricMessagingMessages, newMessagingMessages, groups, senders, - conversationHeaderData); + conversationHeaderData, mSummarizedContent); } /** @@ -1622,6 +1640,9 @@ public class ConversationLayout extends FrameLayout @Nullable public CharSequence getConversationText() { + if (mSummarizedContent != null) { + return mSummarizedContent; + } if (mMessages.isEmpty()) { return null; } diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java index fb1f28fb8ef3..cb5041efd10f 100644 --- a/core/java/com/android/internal/widget/MessagingData.java +++ b/core/java/com/android/internal/widget/MessagingData.java @@ -32,6 +32,7 @@ final class MessagingData { private final List<List<MessagingMessage>> mGroups; private final List<Person> mSenders; private final int mUnreadCount; + private final CharSequence mSummarization; private ConversationHeaderData mConversationHeaderData; @@ -41,8 +42,7 @@ final class MessagingData { List<Person> senders) { this(user, showSpinner, /* unreadCount= */0, historicMessagingMessages, newMessagingMessages, - groups, - senders, null); + groups, senders, null, null); } MessagingData(Person user, boolean showSpinner, @@ -51,7 +51,8 @@ final class MessagingData { List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups, List<Person> senders, - @Nullable ConversationHeaderData conversationHeaderData) { + @Nullable ConversationHeaderData conversationHeaderData, + CharSequence summarization) { mUser = user; mShowSpinner = showSpinner; mUnreadCount = unreadCount; @@ -60,6 +61,7 @@ final class MessagingData { mGroups = groups; mSenders = senders; mConversationHeaderData = conversationHeaderData; + mSummarization = summarization; } public Person getUser() { @@ -94,4 +96,9 @@ final class MessagingData { public ConversationHeaderData getConversationHeaderData() { return mConversationHeaderData; } + + @Nullable + public CharSequence getSummarization() { + return mSummarization; + } } diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index a59ee77cc693..c7f22836dd93 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.Objects; /** - * A message of a {@link MessagingLayout}. + * A message or summary of a {@link MessagingLayout}. */ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index 4ece81c24edc..30dcc67d9ce5 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -132,6 +132,8 @@ public final class NotificationProgressDrawable extends Drawable { final float centerY = (float) getBounds().centerY(); final int numParts = mParts.size(); + final float pointTop = Math.round(centerY - pointRadius); + final float pointBottom = Math.round(centerY + pointRadius); for (int iPart = 0; iPart < numParts; iPart++) { final DrawablePart part = mParts.get(iPart); final float start = left + part.mStart; @@ -146,12 +148,13 @@ public final class NotificationProgressDrawable extends Drawable { mFillPaint.setColor(segment.mColor); - mSegRectF.set(start, centerY - radiusY, end, centerY + radiusY); + mSegRectF.set(Math.round(start), Math.round(centerY - radiusY), Math.round(end), + Math.round(centerY + radiusY)); canvas.drawRoundRect(mSegRectF, cornerRadius, cornerRadius, mFillPaint); } else if (part instanceof DrawablePoint point) { // TODO: b/367804171 - actually use a vector asset for the default point // rather than drawing it as a box? - mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius); + mPointRectF.set(Math.round(start), pointTop, Math.round(end), pointBottom); final float inset = mState.mPointRectInset; final float cornerRadius = mState.mPointRectCornerRadius; mPointRectF.inset(inset, inset); diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java index 2cd4f0362306..52d51539867d 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java @@ -21,6 +21,7 @@ import android.os.Bundle; import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; @@ -45,9 +46,11 @@ import java.util.stream.Stream; */ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibility { private final CoreDocument mDocument; + private final RemoteContext mRemoteContext; - public CoreDocumentAccessibility(CoreDocument document) { + public CoreDocumentAccessibility(CoreDocument document, RemoteContext remoteContext) { this.mDocument = document; + this.mRemoteContext = remoteContext; } @Nullable @@ -95,7 +98,7 @@ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibi @Override public boolean performAction(Component component, int action, Bundle arguments) { if (action == ACTION_CLICK) { - mDocument.performClick(component.getComponentId()); + mDocument.performClick(mRemoteContext, component.getComponentId()); return true; } else { return false; diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java index 010253e9cb95..975383ee36b4 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.view.View; import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.RemoteContextAware; /** * Trivial wrapper for calling setAccessibilityDelegate on a View. This exists primarily because the @@ -31,7 +32,8 @@ public class PlatformRemoteComposeAccessibilityRegistrar View player, @NonNull CoreDocument coreDocument) { return new PlatformRemoteComposeTouchHelper( player, - new CoreDocumentAccessibility(coreDocument), + new CoreDocumentAccessibility( + coreDocument, ((RemoteContextAware) player).getRemoteContext()), new AndroidPlatformSemanticNodeApplier()); } diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java index 43118a0800fb..c8474b19058f 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java @@ -28,6 +28,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.widget.ExploreByTouchHelper; import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.RemoteContextAware; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics; import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent.Mode; @@ -55,7 +56,8 @@ public class PlatformRemoteComposeTouchHelper extends ExploreByTouchHelper { View player, @NonNull CoreDocument coreDocument) { return new PlatformRemoteComposeTouchHelper( player, - new CoreDocumentAccessibility(coreDocument), + new CoreDocumentAccessibility( + coreDocument, ((RemoteContextAware) player).getRemoteContext()), new AndroidPlatformSemanticNodeApplier()); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java index f5f4e4332d28..0cfaf5592d6f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -62,7 +62,7 @@ public class CoreDocument { // We also keep a more fine-grained BUILD number, exposed as // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD - static final float BUILD = 0.3f; + static final float BUILD = 0.4f; @NonNull ArrayList<Operation> mOperations = new ArrayList<>(); @@ -860,16 +860,22 @@ public class CoreDocument { * * @param id the click area id */ - public void performClick(int id) { + public void performClick(@NonNull RemoteContext context, int id) { for (ClickAreaRepresentation clickArea : mClickAreas) { if (clickArea.mId == id) { warnClickListeners(clickArea); return; } } + for (IdActionCallback listener : mIdActionListeners) { listener.onAction(id, ""); } + + Component component = getComponent(id); + if (component != null) { + component.onClick(context, this, -1, -1); + } } /** Warn click listeners when a click area is activated */ diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java index 0b6a3c415e4a..3760af2f7460 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -18,6 +18,7 @@ package com.android.internal.widget.remotecompose.core; import android.annotation.NonNull; import com.android.internal.widget.remotecompose.core.operations.BitmapData; +import com.android.internal.widget.remotecompose.core.operations.BitmapFontData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; import com.android.internal.widget.remotecompose.core.operations.ClipRect; @@ -30,6 +31,7 @@ import com.android.internal.widget.remotecompose.core.operations.DataMapIds; import com.android.internal.widget.remotecompose.core.operations.DataMapLookup; import com.android.internal.widget.remotecompose.core.operations.DrawArc; import com.android.internal.widget.remotecompose.core.operations.DrawBitmap; +import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapScaled; import com.android.internal.widget.remotecompose.core.operations.DrawCircle; @@ -45,6 +47,8 @@ import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath; import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; import com.android.internal.widget.remotecompose.core.operations.FloatConstant; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; +import com.android.internal.widget.remotecompose.core.operations.FloatFunctionCall; +import com.android.internal.widget.remotecompose.core.operations.FloatFunctionDefine; import com.android.internal.widget.remotecompose.core.operations.Header; import com.android.internal.widget.remotecompose.core.operations.IntegerExpression; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; @@ -147,12 +151,14 @@ public class Operations { public static final int DATA_BITMAP = 101; public static final int DATA_SHADER = 45; public static final int DATA_TEXT = 102; + public static final int DATA_BITMAP_FONT = 167; ///////////////////////////// ===================== public static final int CLIP_PATH = 38; public static final int CLIP_RECT = 39; public static final int PAINT_VALUES = 40; public static final int DRAW_RECT = 42; + public static final int DRAW_BITMAP_FONT_TEXT_RUN = 48; public static final int DRAW_TEXT_RUN = 43; public static final int DRAW_CIRCLE = 46; public static final int DRAW_LINE = 47; @@ -196,11 +202,13 @@ public class Operations { public static final int PATH_TWEEN = 158; public static final int PATH_CREATE = 159; public static final int PATH_ADD = 160; - public static final int PARTICLE_CREATE = 161; + public static final int PARTICLE_DEFINE = 161; public static final int PARTICLE_PROCESS = 162; public static final int PARTICLE_LOOP = 163; public static final int IMPULSE_START = 164; public static final int IMPULSE_PROCESS = 165; + public static final int FUNCTION_CALL = 166; + public static final int FUNCTION_DEFINE = 168; ///////////////////////////////////////// ====================== @@ -276,6 +284,7 @@ public class Operations { map.put(HEADER, Header::read); map.put(DRAW_BITMAP_INT, DrawBitmapInt::read); map.put(DATA_BITMAP, BitmapData::read); + map.put(DATA_BITMAP_FONT, BitmapFontData::read); map.put(DATA_TEXT, TextData::read); map.put(THEME, Theme::read); map.put(CLICK_AREA, ClickArea::read); @@ -292,6 +301,7 @@ public class Operations { map.put(DRAW_ROUND_RECT, DrawRoundRect::read); map.put(DRAW_TEXT_ON_PATH, DrawTextOnPath::read); map.put(DRAW_TEXT_RUN, DrawText::read); + map.put(DRAW_BITMAP_FONT_TEXT_RUN, DrawBitmapFontText::read); map.put(DRAW_TWEEN_PATH, DrawTweenPath::read); map.put(DATA_PATH, PathData::read); map.put(PAINT_VALUES, PaintData::read); @@ -389,8 +399,10 @@ public class Operations { map.put(PATH_ADD, PathAppend::read); map.put(IMPULSE_START, ImpulseOperation::read); map.put(IMPULSE_PROCESS, ImpulseProcess::read); - map.put(PARTICLE_CREATE, ParticlesCreate::read); + map.put(PARTICLE_DEFINE, ParticlesCreate::read); map.put(PARTICLE_LOOP, ParticlesLoop::read); + map.put(FUNCTION_CALL, FloatFunctionCall::read); + map.put(FUNCTION_DEFINE, FloatFunctionDefine::read); map.put(ACCESSIBILITY_SEMANTICS, CoreSemantics::read); // map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read); diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java index 1cb8fefde80c..f83ecef1074d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import com.android.internal.widget.remotecompose.core.operations.BitmapData; +import com.android.internal.widget.remotecompose.core.operations.BitmapFontData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; import com.android.internal.widget.remotecompose.core.operations.ClipRect; @@ -33,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.DataMapIds; import com.android.internal.widget.remotecompose.core.operations.DataMapLookup; import com.android.internal.widget.remotecompose.core.operations.DrawArc; import com.android.internal.widget.remotecompose.core.operations.DrawBitmap; +import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapScaled; import com.android.internal.widget.remotecompose.core.operations.DrawCircle; @@ -48,6 +50,8 @@ import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath; import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; import com.android.internal.widget.remotecompose.core.operations.FloatConstant; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; +import com.android.internal.widget.remotecompose.core.operations.FloatFunctionCall; +import com.android.internal.widget.remotecompose.core.operations.FloatFunctionDefine; import com.android.internal.widget.remotecompose.core.operations.Header; import com.android.internal.widget.remotecompose.core.operations.IntegerExpression; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; @@ -557,6 +561,18 @@ public class RemoteComposeBuffer { } /** + * Records a bitmap font and returns an ID. + * + * @param glyphs The glyphs that define the bitmap font + * @return id of the BitmapFont + */ + public int addBitmapFont(BitmapFontData.Glyph[] glyphs) { + int id = mRemoteComposeState.nextId(); + BitmapFontData.apply(mBuffer, id, glyphs); + return id; + } + + /** * This defines the name of the bitmap given the id. * * @param id of the Bitmap @@ -825,6 +841,22 @@ public class RemoteComposeBuffer { } /** + * Draw the text with a bitmap font, with origin at (x,y). The origin is interpreted based on + * the Align setting in the paint. + * + * @param textId The text to be drawn + * @param bitmapFontId The id of the bitmap font to draw with + * @param start The index of the first character in text to draw + * @param end (end - 1) is the index of the last character in text to draw + * @param x The x-coordinate of the origin of the text being drawn + * @param y The y-coordinate of the baseline of the text being drawn + */ + public void addDrawBitmapFontTextRun( + int textId, int bitmapFontId, int start, int end, float x, float y) { + DrawBitmapFontText.apply(mBuffer, textId, bitmapFontId, start, end, x, y); + } + + /** * Draw a text on canvas at relative to position (x, y), offset panX and panY. <br> * The panning factors (panX, panY) mapped to the resulting bounding box of the text, in such a * way that a panning factor of (0.0, 0.0) would center the text at (x, y) @@ -1060,6 +1092,14 @@ public class RemoteComposeBuffer { return "v1.0"; } + /** + * Initialize a buffer from a file + * + * @param path the file path + * @param remoteComposeState the associated state + * @return the RemoteComposeBuffer + * @throws IOException + */ @NonNull public static RemoteComposeBuffer fromFile( @NonNull String path, @NonNull RemoteComposeState remoteComposeState) @@ -1134,11 +1174,24 @@ public class RemoteComposeBuffer { } } + /** + * Read the content of the file into the buffer + * + * @param file a target file + * @param buffer a RemoteComposeBuffer + * @throws IOException + */ static void read(@NonNull File file, @NonNull RemoteComposeBuffer buffer) throws IOException { FileInputStream fd = new FileInputStream(file); read(fd, buffer); } + /** + * Initialize a buffer from an input stream + * + * @param fd the input stream + * @param buffer a RemoteComposeBuffer + */ public static void read(@NonNull InputStream fd, @NonNull RemoteComposeBuffer buffer) { try { byte[] bytes = readAllBytes(fd); @@ -1150,6 +1203,13 @@ public class RemoteComposeBuffer { } } + /** + * Load a byte buffer from the input stream + * + * @param is the input stream + * @return a byte buffer containing the input stream content + * @throws IOException + */ private static byte[] readAllBytes(@NonNull InputStream is) throws IOException { byte[] buff = new byte[32 * 1024]; // moderate size buff to start int red = 0; @@ -1684,7 +1744,27 @@ public class RemoteComposeBuffer { * @return id of the color (color ids are short) */ public short addColorExpression(int alpha, float hue, float sat, float value) { - ColorExpression c = new ColorExpression(0, alpha, hue, sat, value); + ColorExpression c = + new ColorExpression(0, ColorExpression.HSV_MODE, alpha, hue, sat, value); + short id = (short) mRemoteComposeState.cacheData(c); + c.mId = id; + c.write(mBuffer); + return id; + } + + /** + * Color calculated by Alpha, Red, Green and Blue. (as floats they can be variables used to + * create color transitions) + * + * @param alpha the alpha value of the color + * @param red the red component of the color + * @param green the green component of the color + * @param blue the blue component of the color + * @return id of the color (color ids are short) + */ + public short addColorExpression(float alpha, float red, float green, float blue) { + ColorExpression c = + new ColorExpression(0, ColorExpression.ARGB_MODE, alpha, red, green, blue); short id = (short) mRemoteComposeState.cacheData(c); c.mId = id; c.write(mBuffer); @@ -2179,10 +2259,21 @@ public class RemoteComposeBuffer { textAlign); } + /** + * Returns the next available id for the given type + * + * @param type the type of the value + * @return a unique id + */ public int createID(int type) { return mRemoteComposeState.nextId(type); } + /** + * Returns the next available id + * + * @return a unique id + */ public int nextId() { return mRemoteComposeState.nextId(); } @@ -2243,4 +2334,27 @@ public class RemoteComposeBuffer { public void addParticleLoopEnd() { ContainerEnd.apply(mBuffer); } + + /** + * @param fid The id of the function + * @param args The arguments of the function + */ + public void defineFloatFunction(int fid, int[] args) { + FloatFunctionDefine.apply(mBuffer, fid, args); + } + + /** end the definition of the function */ + public void addEndFloatFunctionDef() { + ContainerEnd.apply(mBuffer); + } + + /** + * add a function call + * + * @param id the id of the function to call + * @param args the arguments of the function + */ + public void callFloatFunction(int id, float[] args) { + FloatFunctionCall.apply(mBuffer, id, args); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContextAware.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContextAware.java new file mode 100644 index 000000000000..bf9a8c02d525 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContextAware.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core; + +/** + * This interface defines a contract for objects that are aware of a {@link RemoteContext}. + * + * <p>PlayerViews should implement to provide access to the RemoteContext. + */ +public interface RemoteContextAware { + + /** + * Returns the remote context + * + * @return a RemoteContext + */ + RemoteContext getRemoteContext(); +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java new file mode 100644 index 000000000000..cbd30dc21caf --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; + +import java.util.Arrays; +import java.util.List; + +/** Operation to deal with bitmap font data. */ +public class BitmapFontData extends Operation { + private static final int OP_CODE = Operations.DATA_BITMAP_FONT; + private static final String CLASS_NAME = "BitmapFontData"; + + int mId; + + // Sorted in order of decreasing mChars length. + @NonNull Glyph[] mFontGlyphs; + + /** + * A bitmap font is comprised of a collection of Glyphs. Note each Glyph has its own bitmap + * rather than using a texture atlas. + */ + public static class Glyph { + /** The character(s) this glyph represents. */ + public String mChars; + + /** The id of the bitmap for this glyph, or -1 for space. */ + public int mBitmapId; + + /** The margin in pixels to the left of the glyph bitmap. */ + public short mMarginLeft; + + /** The margin in pixels above of the glyph bitmap. */ + public short mMarginTop; + + /** The margin in pixels to the right of the glyph bitmap. */ + public short mMarginRight; + + /** The margin in pixels below the glyph bitmap. */ + public short mMarginBottom; + + public short mBitmapWidth; + public short mBitmapHeight; + + public Glyph() {} + + public Glyph( + String chars, + int bitmapId, + short marginLeft, + short marginTop, + short marginRight, + short marginBottom, + short width, + short height) { + mChars = chars; + mBitmapId = bitmapId; + mMarginLeft = marginLeft; + mMarginTop = marginTop; + mMarginRight = marginRight; + mMarginBottom = marginBottom; + mBitmapWidth = width; + mBitmapHeight = height; + } + } + + /** + * create a bitmap font structure. + * + * @param id the id of the bitmap font + * @param fontGlyphs the glyphs that define the bitmap font + */ + public BitmapFontData(int id, @NonNull Glyph[] fontGlyphs) { + mId = id; + mFontGlyphs = fontGlyphs; + + // Sort in order of decreasing mChars length. + Arrays.sort(mFontGlyphs, (o1, o2) -> o2.mChars.length() - o1.mChars.length()); + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mId, mFontGlyphs); + } + + @NonNull + @Override + public String toString() { + return "BITMAP FONT DATA " + mId; + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * Add the image to the document + * + * @param buffer document to write to + * @param id the id the bitmap font will be stored under + * @param glyphs glyph metadata + */ + public static void apply(@NonNull WireBuffer buffer, int id, @NonNull Glyph[] glyphs) { + buffer.start(OP_CODE); + buffer.writeInt(id); + buffer.writeInt(glyphs.length); + for (Glyph element : glyphs) { + buffer.writeUTF8(element.mChars); + buffer.writeInt(element.mBitmapId); + buffer.writeShort(element.mMarginLeft); + buffer.writeShort(element.mMarginTop); + buffer.writeShort(element.mMarginRight); + buffer.writeShort(element.mMarginBottom); + buffer.writeShort(element.mBitmapWidth); + buffer.writeShort(element.mBitmapHeight); + } + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int id = buffer.readInt(); + int numGlyphElements = buffer.readInt(); + Glyph[] glyphs = new Glyph[numGlyphElements]; + for (int i = 0; i < numGlyphElements; i++) { + glyphs[i] = new Glyph(); + glyphs[i].mChars = buffer.readUTF8(); + glyphs[i].mBitmapId = buffer.readInt(); + glyphs[i].mMarginLeft = (short) buffer.readShort(); + glyphs[i].mMarginTop = (short) buffer.readShort(); + glyphs[i].mMarginRight = (short) buffer.readShort(); + glyphs[i].mMarginBottom = (short) buffer.readShort(); + glyphs[i].mBitmapWidth = (short) buffer.readShort(); + glyphs[i].mBitmapHeight = (short) buffer.readShort(); + } + + operations.add(new BitmapFontData(id, glyphs)); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Data Operations", OP_CODE, CLASS_NAME) + .description("Bitmap font data") + .field(DocumentedOperation.INT, "id", "id of bitmap font data") + .field(INT_ARRAY, "glyphNodes", "list used to greedily convert strings into glyphs") + .field(INT_ARRAY, "glyphElements", ""); + } + + @Override + public void apply(@NonNull RemoteContext context) { + context.putObject(mId, this); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return indent + toString(); + } + + /** Finds the largest glyph matching the string at the specified offset, or returns null. */ + @Nullable + public Glyph lookupGlyph(String string, int offset) { + // Since mFontGlyphs is sorted on decreasing size, it will match the longest items first. + // It is expected that the mFontGlyphs array will be fairly small. + for (Glyph glyph : mFontGlyphs) { + if (string.startsWith(glyph.mChars, offset)) { + return glyph; + } + } + return null; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java index b385ecd9e5f7..73f99ccb4405 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java @@ -38,7 +38,13 @@ public class ColorExpression extends Operation implements VariableSupport { private static final int OP_CODE = Operations.COLOR_EXPRESSIONS; private static final String CLASS_NAME = "ColorExpression"; public int mId; + + /** + * Mode of the color expression 0 = two colors and a tween 1 = color1 is a colorID. 2 color2 is + * a colorID. 3 = color1 & color2 are ids 4 = H S V mode 5 = RGB mode 6 = ARGB mode + */ int mMode; + public int mColor1; public int mColor2; public float mTween = 0.0f; @@ -51,11 +57,49 @@ public class ColorExpression extends Operation implements VariableSupport { public float mOutValue = 0; public int mAlpha = 0xFF; // only used in hsv mode + private float mArgbAlpha = 0.0f; + private float mArgbRed = 0.0f; + private float mArgbGreen = 0.0f; + private float mArgbBlue = 0.0f; + + private float mOutArgbAlpha = 0.0f; + private float mOutArgbRed = 0.0f; + private float mOutArgbGreen = 0.0f; + private float mOutArgbBlue = 0.0f; + public float mOutTween = 0.0f; public int mOutColor1; public int mOutColor2; - public static final int HSV_MODE = 4; + /** COLOR_COLOR_INTERPOLATE */ + public static final byte COLOR_COLOR_INTERPOLATE = 0; + + /** COLOR_ID_INTERPOLATE */ + public static final byte ID_COLOR_INTERPOLATE = 1; + + /** ID_COLOR_INTERPOLATE */ + public static final byte COLOR_ID_INTERPOLATE = 2; + + /** ID_ID_INTERPOLATE */ + public static final byte ID_ID_INTERPOLATE = 3; + + /** H S V mode */ + public static final byte HSV_MODE = 4; + + /** ARGB mode */ + public static final byte ARGB_MODE = 5; + + /** ARGB mode with a being an id */ + public static final byte IDARGB_MODE = 6; + + /** + * Create a new ColorExpression object + * + * @param id the id of the color + * @param hue the hue of the color + * @param sat the saturation of the color + * @param value the value of the color + */ public ColorExpression(int id, float hue, float sat, float value) { mMode = HSV_MODE; mAlpha = 0xFF; @@ -67,7 +111,21 @@ public class ColorExpression extends Operation implements VariableSupport { mTween = value; } - public ColorExpression(int id, int alpha, float hue, float sat, float value) { + /** + * Create a new ColorExpression object based on HSV + * + * @param id id of the color + * @param mode the mode of the color + * @param alpha the alpha of the color + * @param hue the hue of the color + * @param sat the saturation of the color + * @param value the value (brightness) of the color + */ + public ColorExpression(int id, byte mode, int alpha, float hue, float sat, float value) { + if (mode != HSV_MODE) { + throw new RuntimeException("Invalid mode " + mode); + } + mId = id; mMode = HSV_MODE; mAlpha = alpha; mOutHue = mHue = hue; @@ -78,6 +136,15 @@ public class ColorExpression extends Operation implements VariableSupport { mTween = value; } + /** + * Create a new ColorExpression object based interpolationg two colors + * + * @param id the id of the color + * @param mode the type of mode (are colors ids or actual values) + * @param color1 the first color to use + * @param color2 the second color to use + * @param tween the value to use to interpolate between the two colors + */ public ColorExpression(int id, int mode, int color1, int color2, float tween) { this.mId = id; this.mMode = mode & 0xFF; @@ -95,6 +162,28 @@ public class ColorExpression extends Operation implements VariableSupport { this.mOutColor2 = color2; } + /** + * Create a new ColorExpression object based on ARGB + * + * @param id the id of the color + * @param mode the mode must be ARGB_MODE + * @param alpha the alpha value of the color + * @param red the red of component the color + * @param green the greej component of the color + * @param blue the blue of component the color + */ + public ColorExpression(int id, byte mode, float alpha, float red, float green, float blue) { + if (mode != ARGB_MODE) { + throw new RuntimeException("Invalid mode " + mode); + } + mMode = ARGB_MODE; + mId = id; + mOutArgbAlpha = mArgbAlpha = alpha; + mOutArgbRed = mArgbRed = red; + mOutArgbGreen = mArgbGreen = green; + mOutArgbBlue = mArgbBlue = blue; + } + @Override public void updateVariables(@NonNull RemoteContext context) { if (mMode == 4) { @@ -108,6 +197,20 @@ public class ColorExpression extends Operation implements VariableSupport { mOutValue = context.getFloat(Utils.idFromNan(mValue)); } } + if (mMode == ARGB_MODE) { + if (Float.isNaN(mArgbAlpha)) { + mOutArgbAlpha = context.getFloat(Utils.idFromNan(mArgbAlpha)); + } + if (Float.isNaN(mArgbRed)) { + mOutArgbRed = context.getFloat(Utils.idFromNan(mArgbRed)); + } + if (Float.isNaN(mArgbGreen)) { + mOutArgbGreen = context.getFloat(Utils.idFromNan(mArgbGreen)); + } + if (Float.isNaN(mArgbBlue)) { + mOutArgbBlue = context.getFloat(Utils.idFromNan(mArgbBlue)); + } + } if (Float.isNaN(mTween)) { mOutTween = context.getFloat(Utils.idFromNan(mTween)); } @@ -146,13 +249,21 @@ public class ColorExpression extends Operation implements VariableSupport { @Override public void apply(@NonNull RemoteContext context) { - if (mMode == 4) { + if (mMode == HSV_MODE) { context.loadColor( mId, (mAlpha << 24) | (0xFFFFFF & Utils.hsvToRgb(mOutHue, mOutSat, mOutValue))); return; } + if (mMode == ARGB_MODE) { + context.loadColor( + mId, Utils.toARGB(mOutArgbAlpha, mOutArgbRed, mOutArgbGreen, mOutArgbBlue)); + return; + } if (mOutTween == 0.0) { - context.loadColor(mId, mColor1); + if ((mMode & 1) == 1) { + mOutColor1 = context.getColor(mColor1); + } + context.loadColor(mId, mOutColor1); } else { if ((mMode & 1) == 1) { mOutColor1 = context.getColor(mColor1); @@ -167,14 +278,36 @@ public class ColorExpression extends Operation implements VariableSupport { @Override public void write(@NonNull WireBuffer buffer) { - int mode = mMode | (mAlpha << 16); - apply(buffer, mId, mode, mColor1, mColor2, mTween); + int mode; + switch (mMode) { + case ARGB_MODE: + apply(buffer, mId, mArgbAlpha, mArgbRed, mArgbGreen, mArgbBlue); + break; + + case HSV_MODE: + mOutValue = mValue; + mColor1 = Float.floatToRawIntBits(mHue); + mColor2 = Float.floatToRawIntBits(mSat); + mode = mMode | (mAlpha << 16); + apply(buffer, mId, mode, mColor1, mColor2, mTween); + + break; + case COLOR_ID_INTERPOLATE: + case ID_COLOR_INTERPOLATE: + case ID_ID_INTERPOLATE: + case COLOR_COLOR_INTERPOLATE: + apply(buffer, mId, mMode, mColor1, mColor2, mTween); + + break; + default: + throw new RuntimeException("Invalid mode "); + } } @NonNull @Override public String toString() { - if (mMode == 4) { + if (mMode == HSV_MODE) { return "ColorExpression[" + mId + "] = hsv (" @@ -185,7 +318,20 @@ public class ColorExpression extends Operation implements VariableSupport { + Utils.floatToString(mValue) + ")"; } - + Utils.log(" ColorExpression toString" + mId + " " + mMode); + if (mMode == ARGB_MODE) { + return "ColorExpression[" + + mId + + "] = rgb (" + + Utils.floatToString(mArgbAlpha) + + ", " + + Utils.floatToString(mArgbRed) + + ", " + + Utils.floatToString(mArgbGreen) + + ", " + + Utils.floatToString(mArgbRed) + + ")"; + } String c1 = (mMode & 1) == 1 ? "[" + mColor1 + "]" : Utils.colorInt(mColor1); String c2 = (mMode & 2) == 2 ? "[" + mColor2 + "]" : Utils.colorInt(mColor2); return "ColorExpression[" @@ -230,12 +376,38 @@ public class ColorExpression extends Operation implements VariableSupport { */ public static void apply( @NonNull WireBuffer buffer, int id, int mode, int color1, int color2, float tween) { + apply(buffer, id, mode, color1, color2, Float.floatToRawIntBits(tween)); + } + + /** + * Call to write a ColorExpression object on the buffer + * + * @param buffer + * @param id of the ColorExpression object + * @param alpha + * @param red + * @param green + * @param blue + */ + public static void apply( + @NonNull WireBuffer buffer, int id, float alpha, float red, float green, float blue) { + int param1 = (Float.isNaN(alpha)) ? IDARGB_MODE : ARGB_MODE; + param1 |= + (Float.isNaN(alpha)) ? Utils.idFromNan(alpha) << 16 : ((int) (alpha * 1024)) << 16; + int param2 = Float.floatToRawIntBits(red); + int param3 = Float.floatToRawIntBits(green); + int param4 = Float.floatToRawIntBits(blue); + apply(buffer, id, param1, param2, param3, param4); + } + + private static void apply( + @NonNull WireBuffer buffer, int id, int param1, int param2, int param3, int param4) { buffer.start(OP_CODE); buffer.writeInt(id); - buffer.writeInt(mode); - buffer.writeInt(color1); - buffer.writeInt(color2); - buffer.writeFloat(tween); + buffer.writeInt(param1); + buffer.writeInt(param2); + buffer.writeInt(param3); + buffer.writeInt(param4); } /** @@ -246,12 +418,48 @@ public class ColorExpression extends Operation implements VariableSupport { */ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); - int mode = buffer.readInt(); - int color1 = buffer.readInt(); - int color2 = buffer.readInt(); - float tween = buffer.readFloat(); - - operations.add(new ColorExpression(id, mode, color1, color2, tween)); + int param1 = buffer.readInt(); + int param2 = buffer.readInt(); + int param3 = buffer.readInt(); + int param4 = buffer.readInt(); + int mode = param1 & 0xFF; + float alpha; + float red; + float green; + float blue; + switch (mode) { + case IDARGB_MODE: + alpha = Utils.asNan(param1 >> 16); + red = Float.intBitsToFloat(param2); + green = Float.intBitsToFloat(param3); + blue = Float.intBitsToFloat(param4); + operations.add(new ColorExpression(id, (byte) ARGB_MODE, alpha, red, green, blue)); + break; + case ARGB_MODE: + alpha = (param1 >> 16) / 1024.0f; + red = Float.intBitsToFloat(param2); + green = Float.intBitsToFloat(param3); + blue = Float.intBitsToFloat(param4); + operations.add(new ColorExpression(id, (byte) ARGB_MODE, alpha, red, green, blue)); + break; + case HSV_MODE: + alpha = (param1 >> 16) / 1024.0f; + float hue = Float.intBitsToFloat(param2); + float sat = Float.intBitsToFloat(param3); + float value = Float.intBitsToFloat(param4); + operations.add(new ColorExpression(id, HSV_MODE, (param1 >> 16), hue, sat, value)); + break; + case COLOR_ID_INTERPOLATE: + case ID_COLOR_INTERPOLATE: + case ID_ID_INTERPOLATE: + case COLOR_COLOR_INTERPOLATE: + operations.add( + new ColorExpression( + id, mode, param2, param3, Float.intBitsToFloat(param4))); + break; + default: + throw new RuntimeException("Invalid mode " + mode); + } } /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java index 7f1ba6f94065..411353bd3509 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java @@ -31,8 +31,8 @@ import java.util.List; /** Base class for commands that take 3 float */ public abstract class DrawBase2 extends PaintOperation implements VariableSupport { @NonNull protected String mName = "DrawRectBase"; - protected float mV1; - protected float mV2; + float mV1; + float mV2; float mValue1; float mValue2; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java new file mode 100644 index 000000000000..258988e8b00a --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; + +import android.annotation.NonNull; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; + +import java.util.List; + +/** Draw Text */ +public class DrawBitmapFontText extends PaintOperation implements VariableSupport { + private static final int OP_CODE = Operations.DRAW_BITMAP_FONT_TEXT_RUN; + private static final String CLASS_NAME = "DrawBitmapFontText"; + int mTextID; + int mBitmapFontID; + int mStart; + int mEnd; + float mX; + float mY; + float mOutX; + float mOutY; + + public DrawBitmapFontText(int textID, int bitmapFontID, int start, int end, float x, float y) { + mTextID = textID; + mBitmapFontID = bitmapFontID; + mStart = start; + mEnd = end; + mOutX = mX = x; + mOutY = mY = y; + } + + @Override + public void updateVariables(@NonNull RemoteContext context) { + mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX; + mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY; + } + + @Override + public void registerListening(@NonNull RemoteContext context) { + if (Float.isNaN(mX)) { + context.listensTo(Utils.idFromNan(mX), this); + } + if (Float.isNaN(mY)) { + context.listensTo(Utils.idFromNan(mY), this); + } + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mTextID, mBitmapFontID, mStart, mEnd, mX, mY); + } + + @NonNull + @Override + public String toString() { + return "DrawBitmapFontText [" + + mTextID + + "] " + + mBitmapFontID + + ", " + + mStart + + ", " + + mEnd + + ", " + + floatToString(mX, mOutX) + + ", " + + floatToString(mY, mOutY); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int text = buffer.readInt(); + int bitmapFont = buffer.readInt(); + int start = buffer.readInt(); + int end = buffer.readInt(); + float x = buffer.readFloat(); + float y = buffer.readFloat(); + DrawBitmapFontText op = new DrawBitmapFontText(text, bitmapFont, start, end, x, y); + + operations.add(op); + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * Writes out the operation to the buffer + * + * @param buffer write the command to the buffer + * @param textID id of the text + * @param bitmapFontID id of the bitmap font + * @param start Start position + * @param end end position + * @param x position of where to draw + * @param y position of where to draw + */ + public static void apply( + @NonNull WireBuffer buffer, + int textID, + int bitmapFontID, + int start, + int end, + float x, + float y) { + buffer.start(Operations.DRAW_BITMAP_FONT_TEXT_RUN); + buffer.writeInt(textID); + buffer.writeInt(bitmapFontID); + buffer.writeInt(start); + buffer.writeInt(end); + buffer.writeFloat(x); + buffer.writeFloat(y); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Draw Operations", id(), CLASS_NAME) + .description("Draw a run of bitmap font text, all in a single direction") + .field(DocumentedOperation.INT, "textId", "id of bitmap") + .field(DocumentedOperation.INT, "bitmapFontId", "id of the bitmap font") + .field( + DocumentedOperation.INT, + "start", + "The start of the text to render. -1=end of string") + .field(DocumentedOperation.INT, "end", "The end of the text to render") + .field( + DocumentedOperation.INT, + "contextStart", + "the index of the start of the shaping context") + .field( + DocumentedOperation.INT, + "contextEnd", + "the index of the end of the shaping context") + .field(DocumentedOperation.FLOAT, "x", "The x position at which to draw the text") + .field(DocumentedOperation.FLOAT, "y", "The y position at which to draw the text") + .field(DocumentedOperation.BOOLEAN, "RTL", "Whether the run is in RTL direction"); + } + + @Override + public void paint(@NonNull PaintContext context) { + RemoteContext remoteContext = context.getContext(); + String textToPaint = remoteContext.getText(mTextID); + if (textToPaint == null) { + return; + } + if (mEnd == -1) { + if (mStart != 0) { + textToPaint = textToPaint.substring(mStart); + } + } else if (mEnd > textToPaint.length()) { + textToPaint = textToPaint.substring(mStart); + } else { + textToPaint = textToPaint.substring(mStart, mEnd); + } + + BitmapFontData bitmapFont = (BitmapFontData) remoteContext.getObject(mBitmapFontID); + if (bitmapFont == null) { + return; + } + + float xPos = mX; + int pos = 0; + while (pos < textToPaint.length()) { + BitmapFontData.Glyph glyph = bitmapFont.lookupGlyph(textToPaint, pos); + if (glyph == null) { + pos++; + continue; + } + + pos += glyph.mChars.length(); + if (glyph.mBitmapId == -1) { + // Space is represented by a glyph of -1. + xPos += glyph.mMarginLeft + glyph.mMarginRight; + continue; + } + + xPos += glyph.mMarginLeft; + float xPos2 = xPos + glyph.mBitmapWidth; + context.drawBitmap( + glyph.mBitmapId, xPos, mY + glyph.mMarginTop, xPos2, mY + glyph.mBitmapHeight); + xPos = xPos2 + glyph.mMarginRight; + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java new file mode 100644 index 000000000000..eccc00a18308 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java @@ -0,0 +1,185 @@ +/* + * 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.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression; +import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; + +import java.util.ArrayList; +import java.util.List; + +/** This provides the command to call a floatfunction defined in floatfunction */ +public class FloatFunctionCall extends PaintOperation implements VariableSupport { + private static final int OP_CODE = Operations.FUNCTION_CALL; + private static final String CLASS_NAME = "FunctionCall"; + private final int mId; + private final float[] mArgs; + private final float[] mOutArgs; + + FloatFunctionDefine mFunction; + + @NonNull private ArrayList<Operation> mList = new ArrayList<>(); + + @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression(); + + /** + * Create a new FloatFunctionCall operation + * + * @param id The function to call + * @param args the arguments to call it with + */ + public FloatFunctionCall(int id, float[] args) { + mId = id; + mArgs = args; + if (args != null) { + mOutArgs = new float[args.length]; + System.arraycopy(args, 0, mOutArgs, 0, args.length); + } else { + mOutArgs = null; + } + } + + @Override + public void updateVariables(@NonNull RemoteContext context) { + if (mOutArgs != null) { + for (int i = 0; i < mArgs.length; i++) { + float v = mArgs[i]; + mOutArgs[i] = + (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) + ? context.getFloat(Utils.idFromNan(v)) + : v; + } + } + } + + @Override + public void registerListening(@NonNull RemoteContext context) { + mFunction = (FloatFunctionDefine) context.getObject(mId); + if (mArgs != null) { + for (int i = 0; i < mArgs.length; i++) { + float v = mArgs[i]; + if (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) { + context.listensTo(Utils.idFromNan(v), this); + } + } + } + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mId, mArgs); + } + + @NonNull + @Override + public String toString() { + String str = "callFunction[" + Utils.idString(mId) + "] "; + for (int i = 0; i < mArgs.length; i++) { + str += ((i == 0) ? "" : " ,") + Utils.floatToString(mArgs[i], mOutArgs[i]); + } + return str; + } + + /** + * Write the operation on the buffer + * + * @param buffer the buffer to write to + * @param id the id of the function to call + * @param args the arguments to call the function with + */ + public static void apply(@NonNull WireBuffer buffer, int id, @Nullable float[] args) { + buffer.start(OP_CODE); + buffer.writeInt(id); + if (args != null) { + buffer.writeInt(args.length); + for (int i = 0; i < args.length; i++) { + buffer.writeFloat(args[i]); + } + } else { + buffer.writeInt(0); + } + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int id = buffer.readInt(); + int argLen = buffer.readInt(); + float[] args = null; + if (argLen > 0) { + args = new float[argLen]; + for (int i = 0; i < argLen; i++) { + args[i] = buffer.readFloat(); + } + } + + FloatFunctionCall data = new FloatFunctionCall(id, args); + operations.add(data); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Data Operations", OP_CODE, CLASS_NAME) + .description("Command to call the function") + .field(DocumentedOperation.INT, "id", "id of function to call") + .field(INT, "argLen", "the number of Arguments") + .field(FLOAT_ARRAY, "values", "argLen", "array of float arguments"); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return indent + toString(); + } + + @Override + public void paint(@NonNull PaintContext context) { + RemoteContext remoteContext = context.getContext(); + int[] args = mFunction.getArgs(); + for (int j = 0; j < mOutArgs.length; j++) { + remoteContext.loadFloat(args[j], mOutArgs[j]); + updateVariables(remoteContext); + } + mFunction.execute(remoteContext); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionDefine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionDefine.java new file mode 100644 index 000000000000..efd4eecb807e --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionDefine.java @@ -0,0 +1,168 @@ +/* + * 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.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; + +import android.annotation.NonNull; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.Container; +import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression; + +import java.util.ArrayList; +import java.util.List; + +/** + * This defines a function Operator. It contains a collection of commands which are then executed by + * the FloatFunctionCall command + */ +public class FloatFunctionDefine extends Operation implements VariableSupport, Container { + private static final int OP_CODE = Operations.FUNCTION_DEFINE; + private static final String CLASS_NAME = "FunctionDefine"; + private final int mId; + private final int[] mFloatVarId; + @NonNull private ArrayList<Operation> mList = new ArrayList<>(); + + @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression(); + + /** + * @param id The id of the function + * @param floatVarId the ids of the variables + */ + public FloatFunctionDefine(int id, int[] floatVarId) { + mId = id; + mFloatVarId = floatVarId; + } + + @NonNull + @Override + public ArrayList<Operation> getList() { + return mList; + } + + @Override + public void updateVariables(@NonNull RemoteContext context) {} + + @Override + public void registerListening(@NonNull RemoteContext context) { + context.putObject(mId, this); + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mId, mFloatVarId); + } + + @NonNull + @Override + public String toString() { + String str = "FloatFunctionDefine[" + Utils.idString(mId) + "] ("; + for (int j = 0; j < mFloatVarId.length; j++) { + str += "[" + mFloatVarId[j] + "] "; + } + str += ")"; + for (Operation operation : mList) { + str += " \n " + operation.toString(); + } + return str; + } + + /** + * Write the operation on the buffer + * + * @param buffer the buffer to write to + * @param id the id of the function + * @param varId the ids of the variables + */ + public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] varId) { + buffer.start(OP_CODE); + buffer.writeInt(id); + buffer.writeInt(varId.length); + for (int i = 0; i < varId.length; i++) { + buffer.writeInt(varId[i]); + } + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int id = buffer.readInt(); + int varLen = buffer.readInt(); + int[] varId = new int[varLen]; + for (int i = 0; i < varId.length; i++) { + varId[i] = buffer.readInt(); + } + FloatFunctionDefine data = new FloatFunctionDefine(id, varId); + operations.add(data); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Data Operations", OP_CODE, CLASS_NAME) + .description("Define a function") + .field(DocumentedOperation.INT, "id", "The reference of the function") + .field(INT, "varLen", "number of arguments to the function") + .field(FLOAT_ARRAY, "id", "varLen", "id equations"); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return indent + toString(); + } + + /** + * @return the array of id's + */ + public int[] getArgs() { + return mFloatVarId; + } + + @Override + public void apply(@NonNull RemoteContext context) {} + + /** + * Execute the function by applying the list of operations + * + * @param context the current RemoteContext + */ + public void execute(@NonNull RemoteContext context) { + for (Operation op : mList) { + if (op instanceof VariableSupport) { + ((VariableSupport) op).updateVariables(context); + } + + context.incrementOpCount(); + op.apply(context); + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java index 9e891c48c065..ee9e7a4045cb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java @@ -39,7 +39,7 @@ import java.util.List; * for constructing the particles */ public class ParticlesCreate extends Operation implements VariableSupport { - private static final int OP_CODE = Operations.PARTICLE_CREATE; + private static final int OP_CODE = Operations.PARTICLE_DEFINE; private static final String CLASS_NAME = "ParticlesCreate"; private final int mId; private final float[][] mEquations; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java index 791079070622..8d19c94df604 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java @@ -38,7 +38,7 @@ import java.util.ArrayList; import java.util.List; /** - * This provides the mechinism to evolve the particles It consist of a restart equation and a list + * This provides the mechanism to evolve the particles It consist of a restart equation and a list * of equations particle restarts if restart equation > 0 */ public class ParticlesLoop extends PaintOperation implements VariableSupport, Container { @@ -159,10 +159,10 @@ public class ParticlesLoop extends PaintOperation implements VariableSupport, Co /** * Write the operation on the buffer * - * @param buffer - * @param id - * @param restart - * @param equations + * @param buffer the buffer to write to + * @param id the id of the particle system + * @param restart the restart equation + * @param equations the equations to evolve the particles */ public static void apply( @NonNull WireBuffer buffer, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java index bd68d5a8c180..5f505409e254 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java @@ -289,4 +289,21 @@ public class Utils { } return 0; } + + /** + * Convert float alpha, red,g reen, blue to ARGB int + * + * @param alpha alpha value + * @param red red value + * @param green green value + * @param blue blue value + * @return ARGB int + */ + public static int toARGB(float alpha, float red, float green, float blue) { + int a = (int) (alpha * 255.0f + 0.5f); + int r = (int) (red * 255.0f + 0.5f); + int g = (int) (green * 255.0f + 0.5f); + int b = (int) (blue * 255.0f + 0.5f); + return (a << 24 | r << 16 | g << 8 | b); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java index 8e733ce1d808..96a31aec7dc4 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java @@ -37,13 +37,15 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; +import com.android.internal.widget.remotecompose.core.serialize.Serializable; import java.util.ArrayList; import java.util.HashSet; /** Generic Component class */ public class Component extends PaintOperation - implements Container, Measurable, SerializableToString { + implements Container, Measurable, SerializableToString, Serializable { private static final boolean DEBUG = false; @@ -61,7 +63,7 @@ public class Component extends PaintOperation public boolean mNeedsMeasure = true; public boolean mNeedsRepaint = false; @Nullable public AnimateMeasure mAnimateMeasure; - @NonNull public AnimationSpec mAnimationSpec = new AnimationSpec(); + @NonNull public AnimationSpec mAnimationSpec = AnimationSpec.DEFAULT; public boolean mFirstLayout = true; @NonNull PaintBundle mPaint = new PaintBundle(); @NonNull protected HashSet<ComponentValue> mComponentValues = new HashSet<>(); @@ -318,6 +320,14 @@ public class Component extends PaintOperation } } + protected AnimationSpec getAnimationSpec() { + return mAnimationSpec; + } + + protected void setAnimationSpec(@NonNull AnimationSpec animationSpec) { + mAnimationSpec = animationSpec; + } + public enum Visibility { GONE, VISIBLE, @@ -501,16 +511,17 @@ public class Component extends PaintOperation * * @param context * @param document - * @param x - * @param y + * @param x x location on screen or -1 if unconditional click + * @param y y location on screen or -1 if unconditional click */ public void onClick( @NonNull RemoteContext context, @NonNull CoreDocument document, float x, float y) { - if (!contains(x, y)) { + boolean isUnconditional = x == -1 & y == -1; + if (!isUnconditional && !contains(x, y)) { return; } - float cx = x - getScrollX(); - float cy = y - getScrollY(); + float cx = isUnconditional ? -1 : x - getScrollX(); + float cy = isUnconditional ? -1 : y - getScrollY(); for (Operation op : mList) { if (op instanceof Component) { ((Component) op).onClick(context, document, cx, cy); @@ -1035,4 +1046,15 @@ public class Component extends PaintOperation } return null; } + + @Override + public void serialize(MapSerializer serializer) { + serializer.add("type", getSerializedName()); + serializer.add("id", mComponentId); + serializer.add("x", mX); + serializer.add("y", mY); + serializer.add("width", mWidth); + serializer.add("height", mHeight); + serializer.add("visibility", mVisibility); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java index dcd334822010..c517e50f35d3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate import com.android.internal.widget.remotecompose.core.operations.PaintData; import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.TouchExpression; +import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation; @@ -44,6 +45,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.ArrayList; @@ -234,6 +236,8 @@ public class LayoutComponent extends Component { mZIndexModifier = (ZIndexModifierOperation) op; } else if (op instanceof GraphicsLayerModifierOperation) { mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op; + } else if (op instanceof AnimationSpec) { + mAnimationSpec = (AnimationSpec) op; } else if (op instanceof ScrollDelegate) { ScrollDelegate scrollDelegate = (ScrollDelegate) op; if (scrollDelegate.handlesHorizontalScroll()) { @@ -256,6 +260,16 @@ public class LayoutComponent extends Component { if (heightInConstraints != null) { mHeightModifier.setHeightIn(heightInConstraints); } + + if (mAnimationSpec != AnimationSpec.DEFAULT) { + for (int i = 0; i < mChildrenComponents.size(); i++) { + Component c = mChildrenComponents.get(i); + if (c != null && c.getAnimationSpec() == AnimationSpec.DEFAULT) { + c.setAnimationSpec(mAnimationSpec); + } + } + } + setWidth(computeModifierDefinedWidth(null)); setHeight(computeModifierDefinedHeight(null)); } @@ -473,4 +487,14 @@ public class LayoutComponent extends Component { public ArrayList<Component> getChildrenComponents() { return mChildrenComponents; } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("children", mChildrenComponents); + serializer.add("paddingLeft", mPaddingLeft); + serializer.add("paddingRight", mPaddingRight); + serializer.add("paddingTop", mPaddingTop); + serializer.add("paddingBottom", mPaddingBottom); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java index 4977a15e2dc1..a4e8f5c5f18e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java @@ -94,6 +94,11 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement return "TouchCancelModifier"; } + /** + * Write the operation on the buffer + * + * @param buffer a WireBuffer + */ public static void apply(WireBuffer buffer) { buffer.start(OP_CODE); } @@ -108,6 +113,11 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement operations.add(new TouchCancelModifierOperation()); } + /** + * Add documentation for this operation + * + * @param doc a DocumentationBuilder + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, name()) .description( diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java index 8c51f2eac383..6191bf4e4ad2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java @@ -96,14 +96,30 @@ public class TouchDownModifierOperation extends ListActionsOperation implements return "TouchModifier"; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + */ public static void apply(WireBuffer buffer) { buffer.start(OP_CODE); } + /** + * Read the operation from the buffer + * + * @param buffer a WireBuffer + * @param operations the list of operations we read so far + */ public static void read(WireBuffer buffer, List<Operation> operations) { operations.add(new TouchDownModifierOperation()); } + /** + * Add documentation for this operation + * + * @param doc a DocumentationBuilder + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, name()) .description( diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java index a12c356f7c48..a7e423e67940 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java @@ -94,14 +94,30 @@ public class TouchUpModifierOperation extends ListActionsOperation implements To return "TouchUpModifier"; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + */ public static void apply(WireBuffer buffer) { buffer.start(OP_CODE); } + /** + * Read the operation from the buffer + * + * @param buffer a WireBuffer + * @param operations the list of operations we read so far + */ public static void read(WireBuffer buffer, List<Operation> operations) { operations.add(new TouchUpModifierOperation()); } + /** + * Add documentation for this operation + * + * @param doc a DocumentationBuilder + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, name()) .description( diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java index d3b3e0e775f2..e5cd485967e8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java @@ -38,8 +38,8 @@ public class AnimateMeasure { private final @NonNull Component mComponent; private final @NonNull ComponentMeasure mOriginal; private final @NonNull ComponentMeasure mTarget; - private int mDuration; - private int mDurationVisibilityChange = mDuration; + private float mDuration; + private float mDurationVisibilityChange = mDuration; private @NonNull AnimationSpec.ANIMATION mEnterAnimation = AnimationSpec.ANIMATION.FADE_IN; private @NonNull AnimationSpec.ANIMATION mExitAnimation = AnimationSpec.ANIMATION.FADE_OUT; private int mMotionEasingType = GeneralEasing.CUBIC_STANDARD; @@ -64,8 +64,8 @@ public class AnimateMeasure { @NonNull Component component, @NonNull ComponentMeasure original, @NonNull ComponentMeasure target, - int duration, - int durationVisibilityChange, + float duration, + float durationVisibilityChange, @NonNull AnimationSpec.ANIMATION enterAnimation, @NonNull AnimationSpec.ANIMATION exitAnimation, int motionEasingType, @@ -94,6 +94,11 @@ public class AnimateMeasure { component.mVisibility = target.getVisibility(); } + /** + * Update the current bounds/visibility/etc given the current time + * + * @param currentTime the time we use to evaluate the animation + */ public void update(long currentTime) { long elapsed = currentTime - mStartTime; float motionProgress = elapsed / (float) mDuration; @@ -347,6 +352,11 @@ public class AnimateMeasure { return mOriginal.getH() * (1 - mP) + mTarget.getH() * mP; } + /** + * Returns the visibility for this measure + * + * @return the current visibility (possibly interpolated) + */ public float getVisibility() { if (mOriginal.getVisibility() == mTarget.getVisibility()) { return 1f; @@ -357,6 +367,12 @@ public class AnimateMeasure { } } + /** + * Set the target values from the given measure + * + * @param measure the target measure + * @param currentTime the current time + */ public void updateTarget(@NonNull ComponentMeasure measure, long currentTime) { mOriginal.setX(getX()); mOriginal.setY(getY()); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java index 6dff4a87088b..6e9de58e354a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java @@ -24,25 +24,28 @@ import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing; import java.util.List; /** Basic component animation spec */ -public class AnimationSpec extends Operation { +public class AnimationSpec extends Operation implements ModifierOperation { + public static final AnimationSpec DEFAULT = new AnimationSpec(); int mAnimationId = -1; - int mMotionDuration = 300; + float mMotionDuration = 300; int mMotionEasingType = GeneralEasing.CUBIC_STANDARD; - int mVisibilityDuration = 300; + float mVisibilityDuration = 300; int mVisibilityEasingType = GeneralEasing.CUBIC_STANDARD; @NonNull ANIMATION mEnterAnimation = ANIMATION.FADE_IN; @NonNull ANIMATION mExitAnimation = ANIMATION.FADE_OUT; public AnimationSpec( int animationId, - int motionDuration, + float motionDuration, int motionEasingType, - int visibilityDuration, + float visibilityDuration, int visibilityEasingType, @NonNull ANIMATION enterAnimation, @NonNull ANIMATION exitAnimation) { @@ -70,7 +73,7 @@ public class AnimationSpec extends Operation { return mAnimationId; } - public int getMotionDuration() { + public float getMotionDuration() { return mMotionDuration; } @@ -78,7 +81,7 @@ public class AnimationSpec extends Operation { return mMotionEasingType; } - public int getVisibilityDuration() { + public float getVisibilityDuration() { return mVisibilityDuration; } @@ -102,6 +105,25 @@ public class AnimationSpec extends Operation { return "ANIMATION_SPEC (" + mMotionDuration + " ms)"; } + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append( + indent, + "ANIMATION_SPEC = [" + + getMotionDuration() + + ", " + + getMotionEasingType() + + ", " + + getVisibilityDuration() + + ", " + + getVisibilityEasingType() + + ", " + + getEnterAnimation() + + ", " + + getExitAnimation() + + "]"); + } + public enum ANIMATION { FADE_IN, FADE_OUT, @@ -156,10 +178,22 @@ public class AnimationSpec extends Operation { return Operations.ANIMATION_SPEC; } + /** + * Returns an int for the given ANIMATION + * + * @param animation an ANIMATION enum value + * @return a corresponding int value + */ public static int animationToInt(@NonNull ANIMATION animation) { return animation.ordinal(); } + /** + * Maps int value to the corresponding ANIMATION enum values + * + * @param value int value mapped to the enum + * @return the corresponding ANIMATION enum value + */ @NonNull public static ANIMATION intToAnimation(int value) { switch (value) { @@ -184,20 +218,32 @@ public class AnimationSpec extends Operation { } } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param animationId the animation id + * @param motionDuration the duration of the motion animation + * @param motionEasingType the type of easing for the motion animation + * @param visibilityDuration the duration of the visibility animation + * @param visibilityEasingType the type of easing for the visibility animation + * @param enterAnimation the type of animation when "entering" (newly visible) + * @param exitAnimation the type of animation when "exiting" (newly gone) + */ public static void apply( @NonNull WireBuffer buffer, int animationId, - int motionDuration, + float motionDuration, int motionEasingType, - int visibilityDuration, + float visibilityDuration, int visibilityEasingType, @NonNull ANIMATION enterAnimation, @NonNull ANIMATION exitAnimation) { buffer.start(Operations.ANIMATION_SPEC); buffer.writeInt(animationId); - buffer.writeInt(motionDuration); + buffer.writeFloat(motionDuration); buffer.writeInt(motionEasingType); - buffer.writeInt(visibilityDuration); + buffer.writeFloat(visibilityDuration); buffer.writeInt(visibilityEasingType); buffer.writeInt(animationToInt(enterAnimation)); buffer.writeInt(animationToInt(exitAnimation)); @@ -211,9 +257,9 @@ public class AnimationSpec extends Operation { */ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int animationId = buffer.readInt(); - int motionDuration = buffer.readInt(); + float motionDuration = buffer.readFloat(); int motionEasingType = buffer.readInt(); - int visibilityDuration = buffer.readInt(); + float visibilityDuration = buffer.readFloat(); int visibilityEasingType = buffer.readInt(); ANIMATION enterAnimation = intToAnimation(buffer.readInt()); ANIMATION exitAnimation = intToAnimation(buffer.readInt()); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java index 64e2f004cb65..051579b02cee 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java @@ -30,6 +30,15 @@ public class ParticleAnimation { @NonNull PaintBundle mPaint = new PaintBundle(); + /** + * Animate the particle animation + * + * @param context the current paint context + * @param component the target component + * @param start the component's measure at the end of the animation + * @param end the component's measure at the end of the animation + * @param progress the current animation progress + */ public void animate( @NonNull PaintContext context, @NonNull Component component, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java index a37f35f0c8d8..35d639e65385 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java @@ -29,6 +29,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.Componen import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -191,6 +192,15 @@ public class BoxLayout extends LayoutManager { return Operations.LAYOUT_BOX; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param componentId the component id + * @param animationId the component animation id + * @param horizontalPositioning the horizontal positioning rules + * @param verticalPositioning the vertical positioning rules + */ public static void apply( @NonNull WireBuffer buffer, int componentId, @@ -260,4 +270,28 @@ public class BoxLayout extends LayoutManager { public void write(@NonNull WireBuffer buffer) { apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning); } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning)); + serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning)); + } + + private String getPositioningString(int pos) { + switch (pos) { + case START: + return "START"; + case CENTER: + return "CENTER"; + case END: + return "END"; + case TOP: + return "TOP"; + case BOTTOM: + return "BOTTOM"; + default: + return "NONE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java index 0091a47eebfb..8448132cbcc1 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java @@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -91,6 +92,13 @@ public class CanvasLayout extends BoxLayout { return Operations.LAYOUT_CANVAS; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param componentId the component id + * @param animationId the component animation id + */ public static void apply(@NonNull WireBuffer buffer, int componentId, int animationId) { buffer.start(Operations.LAYOUT_CANVAS); buffer.writeInt(componentId); @@ -142,4 +150,27 @@ public class CanvasLayout extends BoxLayout { public void write(@NonNull WireBuffer buffer) { apply(buffer, mComponentId, mAnimationId); } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("", mHorizontalPositioning); + } + + private String getPositioningString(int pos) { + switch (pos) { + case START: + return "START"; + case CENTER: + return "CENTER"; + case END: + return "END"; + case TOP: + return "TOP"; + case BOTTOM: + return "BOTTOM"; + default: + return "NONE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java index 4d0cbefb0c92..47a55b6ed82a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java @@ -34,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -218,6 +219,8 @@ public class ColumnLayout extends LayoutManager { boolean checkWeights = true; while (checkWeights) { checkWeights = false; + childrenWidth = 0f; + childrenHeight = 0f; boolean hasWeights = false; float totalWeights = 0f; for (Component child : mChildrenComponents) { @@ -477,4 +480,35 @@ public class ColumnLayout extends LayoutManager { mVerticalPositioning, mSpacedBy); } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning)); + serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning)); + serializer.add("spacedBy", mSpacedBy); + } + + private String getPositioningString(int pos) { + switch (pos) { + case START: + return "START"; + case CENTER: + return "CENTER"; + case END: + return "END"; + case TOP: + return "TOP"; + case BOTTOM: + return "BOTTOM"; + case SPACE_BETWEEN: + return "SPACE_BETWEEN"; + case SPACE_EVENLY: + return "SPACE_EVENLY"; + case SPACE_AROUND: + return "SPACE_AROUND"; + default: + return "NONE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java index 5b35c4c70702..e93cbd74b0b5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java @@ -34,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -218,6 +219,8 @@ public class RowLayout extends LayoutManager { while (checkWeights) { checkWeights = false; + childrenWidth = 0f; + childrenHeight = 0f; boolean hasWeights = false; float totalWeights = 0f; for (Component child : mChildrenComponents) { @@ -481,4 +484,35 @@ public class RowLayout extends LayoutManager { mVerticalPositioning, mSpacedBy); } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning)); + serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning)); + serializer.add("spacedBy", mSpacedBy); + } + + private String getPositioningString(int pos) { + switch (pos) { + case START: + return "START"; + case CENTER: + return "CENTER"; + case END: + return "END"; + case TOP: + return "TOP"; + case BOTTOM: + return "BOTTOM"; + case SPACE_BETWEEN: + return "SPACE_BETWEEN"; + case SPACE_EVENLY: + return "SPACE_EVENLY"; + case SPACE_AROUND: + return "SPACE_AROUND"; + default: + return "NONE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java index 3044797b17c9..ee16bc2f4459 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java @@ -30,6 +30,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.ArrayList; import java.util.HashMap; @@ -81,6 +82,7 @@ public class StateLayout extends LayoutManager { hideLayoutsOtherThan(currentLayoutIndex); } + /** Traverse the list of children and identify animated components across states */ public void findAnimatedComponents() { for (int i = 0; i < mChildrenComponents.size(); i++) { Component cs = mChildrenComponents.get(i); @@ -105,6 +107,10 @@ public class StateLayout extends LayoutManager { collapsePaintedComponents(); } + /** + * Traverse the list of components in different states, and if they are similar pick the first + * component for painting in all states. + */ public void collapsePaintedComponents() { int numStates = mChildrenComponents.size(); for (Integer id : statePaintedComponents.keySet()) { @@ -346,6 +352,11 @@ public class StateLayout extends LayoutManager { measuredLayoutIndex = currentLayoutIndex; } + /** + * Hides all layouts that are not the one with the given id + * + * @param idx the layout id + */ public void hideLayoutsOtherThan(int idx) { int index = 0; for (Component pane : mChildrenComponents) { @@ -360,6 +371,12 @@ public class StateLayout extends LayoutManager { } } + /** + * Returns the layout with the given id + * + * @param idx the component id + * @return the LayoutManager with the given id, or the first child of StateLayout if not found + */ public @NonNull LayoutManager getLayout(int idx) { int index = 0; for (Component pane : mChildrenComponents) { @@ -485,6 +502,7 @@ public class StateLayout extends LayoutManager { } } + /** Check if we are at the end of the transition, and if so handles it. */ public void checkEndOfTransition() { LayoutManager currentLayout = getLayout(measuredLayoutIndex); LayoutManager previousLayout = getLayout(previousLayoutIndex); @@ -536,10 +554,16 @@ public class StateLayout extends LayoutManager { return "STATE_LAYOUT"; } - // companion object { - // fun documentation(doc: OrigamiDocumentation) {} - // } - + /** + * write the operation to the buffer + * + * @param buffer the current buffer + * @param componentId the component id + * @param animationId the animation id if there's one, -1 otherwise. + * @param horizontalPositioning the horizontal positioning rule + * @param verticalPositioning the vertical positioning rule + * @param indexId the current index + */ public static void apply( @NonNull WireBuffer buffer, int componentId, @@ -570,4 +594,10 @@ public class StateLayout extends LayoutManager { operations.add( new StateLayout(null, componentId, animationId, 0f, 0f, 100f, 100f, indexId)); } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("indexId", mIndexId); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java index 8157ea05ec45..e8e95db8141d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java @@ -34,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -331,6 +332,20 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access return Operations.LAYOUT_TEXT; } + /** + * Write the operation in the buffer + * + * @param buffer the WireBuffer we write on + * @param componentId the component id + * @param animationId the animation id (-1 if not set) + * @param textId the text id + * @param color the text color + * @param fontSize the font size + * @param fontStyle the font style + * @param fontWeight the font weight + * @param fontFamilyId the font family id + * @param textAlign the alignment rules + */ public static void apply( @NonNull WireBuffer buffer, int componentId, @@ -418,4 +433,16 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access mFontFamilyId, mTextAlign); } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("textId", mTextId); + serializer.add("color", mColor); + serializer.add("fontSize", mFontSize); + serializer.add("fontStyle", mFontStyle); + serializer.add("fontWeight", mFontWeight); + serializer.add("fontFamilyId", mFontFamilyId); + serializer.add("textAlign", mTextAlign); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java index 82f23cdcf766..11ed9f435070 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java @@ -92,6 +92,11 @@ public class ComponentMeasure { component.mVisibility); } + /** + * Initialize this ComponentMeasure from another ComponentMeasure instance. + * + * @param m the ComponentMeasure to copy from + */ public void copyFrom(@NonNull ComponentMeasure m) { mX = m.mX; mY = m.mY; @@ -100,6 +105,12 @@ public class ComponentMeasure { mVisibility = m.mVisibility; } + /** + * Returns true if the ComponentMeasure passed is identical to us + * + * @param m the ComponentMeasure to check + * @return true if the passed ComponentMeasure is identical to ourself + */ public boolean same(@NonNull ComponentMeasure m) { return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java index 5cfb1b43cf15..b14f2d9f8a94 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java @@ -28,10 +28,17 @@ import java.util.HashMap; public class MeasurePass { @NonNull HashMap<Integer, ComponentMeasure> mList = new HashMap<>(); + /** Clear the MeasurePass */ public void clear() { mList.clear(); } + /** + * Add a ComponentMeasure to the MeasurePass + * + * @param measure the ComponentMeasure to add + * @throws Exception + */ public void add(@NonNull ComponentMeasure measure) throws Exception { if (measure.mId == -1) { throw new Exception("Component has no id!"); @@ -39,10 +46,22 @@ public class MeasurePass { mList.put(measure.mId, measure); } + /** + * Returns true if the current MeasurePass already contains a ComponentMeasure for the given id. + * + * @param id + * @return + */ public boolean contains(int id) { return mList.containsKey(id); } + /** + * return the ComponentMeasure associated with a given component + * + * @param c the Component + * @return the associated ComponentMeasure + */ public @NonNull ComponentMeasure get(@NonNull Component c) { if (!mList.containsKey(c.getComponentId())) { ComponentMeasure measure = @@ -54,6 +73,12 @@ public class MeasurePass { return mList.get(c.getComponentId()); } + /** + * Returns the ComponentMeasure associated with the id, creating one if none exists. + * + * @param id the component id + * @return the associated ComponentMeasure + */ public @NonNull ComponentMeasure get(int id) { if (!mList.containsKey(id)) { ComponentMeasure measure = diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java index b4240d0e08a7..ac23db0ed599 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java @@ -130,6 +130,20 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer the WireBuffer + * @param x x coordinate of the background rect + * @param y y coordinate of the background rect + * @param width width of the background rect + * @param height height of the background rect + * @param r red component of the background color + * @param g green component of the background color + * @param b blue component of the background color + * @param a alpha component of the background color + * @param shapeType the shape of the background (RECTANGLE=0, CIRCLE=1) + */ public static void apply( @NonNull WireBuffer buffer, float x, @@ -205,6 +219,6 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { .field(FLOAT, "g", "") .field(FLOAT, "b", "") .field(FLOAT, "a", "") - .field(FLOAT, "shapeType", ""); + .field(FLOAT, "shapeType", "0 for RECTANGLE, 1 for CIRCLE"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java index df30d9f615e5..06c21bd49f33 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java @@ -176,6 +176,22 @@ public class BorderModifierOperation extends DecoratorModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer the WireBuffer + * @param x x coordinate of the border rect + * @param y y coordinate of the border rect + * @param width width of the border rect + * @param height height of the border rect + * @param borderWidth the width of the border outline + * @param roundedCorner rounded corner value in pixels + * @param r red component of the border color + * @param g green component of the border color + * @param b blue component of the border color + * @param a alpha component of the border color + * @param shapeType the shape type (0 = RECTANGLE, 1 = CIRCLE) + */ public static void apply( @NonNull WireBuffer buffer, float x, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java index b27fb9200398..ce4449355434 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java @@ -76,6 +76,11 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer the WireBuffer + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java index a1609ace2138..dd27f8b6cfe6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java @@ -21,6 +21,7 @@ import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.SerializableToString; import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; @@ -36,7 +37,7 @@ import java.util.ArrayList; /** Maintain a list of modifiers */ public class ComponentModifiers extends PaintOperation - implements DecoratorComponent, ClickHandler, TouchHandler { + implements DecoratorComponent, ClickHandler, TouchHandler, SerializableToString { @NonNull ArrayList<ModifierOperation> mList = new ArrayList<>(); @NonNull @@ -68,6 +69,7 @@ public class ComponentModifiers extends PaintOperation // nothing } + @Override public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, "MODIFIERS"); for (ModifierOperation m : mList) { @@ -75,10 +77,20 @@ public class ComponentModifiers extends PaintOperation } } + /** + * Add a ModifierOperation + * + * @param operation a ModifierOperation + */ public void add(@NonNull ModifierOperation operation) { mList.add(operation); } + /** + * Returns the size of the modifier list + * + * @return number of modifiers + */ public int size() { return mList.size(); } @@ -133,6 +145,11 @@ public class ComponentModifiers extends PaintOperation } } + /** + * Add the operations to this ComponentModifier + * + * @param operations list of ModifierOperation + */ public void addAll(@NonNull ArrayList<ModifierOperation> operations) { mList.addAll(operations); } @@ -197,6 +214,11 @@ public class ComponentModifiers extends PaintOperation } } + /** + * Returns true if we have a horizontal scroll modifier + * + * @return true if we have a horizontal scroll modifier, false otherwise + */ public boolean hasHorizontalScroll() { for (ModifierOperation op : mList) { if (op instanceof ScrollModifierOperation) { @@ -209,6 +231,11 @@ public class ComponentModifiers extends PaintOperation return false; } + /** + * Returns true if we have a vertical scroll modifier + * + * @return true if we have a vertical scroll modifier, false otherwise + */ public boolean hasVerticalScroll() { for (ModifierOperation op : mList) { if (op instanceof ScrollModifierOperation) { @@ -221,6 +248,12 @@ public class ComponentModifiers extends PaintOperation return false; } + /** + * Set the horizontal scroll dimension (if we have a scroll modifier) + * + * @param hostDimension the host component horizontal dimension + * @param contentDimension the content horizontal dimension + */ public void setHorizontalScrollDimension(float hostDimension, float contentDimension) { for (ModifierOperation op : mList) { if (op instanceof ScrollModifierOperation) { @@ -232,6 +265,12 @@ public class ComponentModifiers extends PaintOperation } } + /** + * Set the vertical scroll dimension (if we have a scroll modifier) + * + * @param hostDimension the host component vertical dimension + * @param contentDimension the content vertical dimension + */ public void setVerticalScrollDimension(float hostDimension, float contentDimension) { for (ModifierOperation op : mList) { if (op instanceof ScrollModifierOperation) { @@ -243,6 +282,11 @@ public class ComponentModifiers extends PaintOperation } } + /** + * Returns the horizontal scroll dimension if we have a scroll modifier + * + * @return the horizontal scroll dimension, or 0 if no scroll modifier + */ public float getHorizontalScrollDimension() { for (ModifierOperation op : mList) { if (op instanceof ScrollModifierOperation) { @@ -255,6 +299,11 @@ public class ComponentModifiers extends PaintOperation return 0f; } + /** + * Returns the vertical scroll dimension if we have a scroll modifier + * + * @return the vertical scroll dimension, or 0 if no scroll modifier + */ public float getVerticalScrollDimension() { for (ModifierOperation op : mList) { if (op instanceof ScrollModifierOperation) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java index c377b756ff38..dd22391c43ac 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java @@ -52,6 +52,11 @@ public class ComponentVisibilityOperation extends Operation return "ComponentVisibilityOperation(" + mVisibilityId + ")"; } + /** + * Returns the serialized name for this operation + * + * @return the serialized name + */ @NonNull public String serializedName() { return "COMPONENT_VISIBILITY"; @@ -74,6 +79,12 @@ public class ComponentVisibilityOperation extends Operation @Override public void write(@NonNull WireBuffer buffer) {} + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param valueId visibility value + */ public static void apply(@NonNull WireBuffer buffer, int valueId) { buffer.start(OP_CODE); buffer.writeInt(valueId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionInModifierOperation.java new file mode 100644 index 000000000000..7c9acfe8d2e6 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionInModifierOperation.java @@ -0,0 +1,109 @@ +/* + * 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.widget.remotecompose.core.operations.layout.modifiers; + +import android.annotation.NonNull; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.Utils; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +/** Helper class to set the min / max dimension on a component */ +public class DimensionInModifierOperation extends Operation + implements ModifierOperation, VariableSupport { + int mOpCode = -1; + + float mV1; + float mV2; + float mValue1; + float mValue2; + + public DimensionInModifierOperation(int opcode, float min, float max) { + mOpCode = opcode; + mValue1 = min; + mValue2 = max; + if (!Float.isNaN(mValue1)) { + mV1 = mValue1; + } + if (!Float.isNaN(mValue2)) { + mV2 = mValue2; + } + } + + @Override + public void updateVariables(@NonNull RemoteContext context) { + mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; + mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; + if (mV1 != -1) { + mV1 = mV1 * context.getDensity(); + } + if (mV2 != -1) { + mV2 = mV2 * context.getDensity(); + } + } + + @Override + public void registerListening(@NonNull RemoteContext context) { + if (Float.isNaN(mValue1)) { + context.listensTo(Utils.idFromNan(mValue1), this); + } + if (Float.isNaN(mValue2)) { + context.listensTo(Utils.idFromNan(mValue2), this); + } + } + + @Override + public void write(@NonNull WireBuffer buffer) { + // nothing + } + + @Override + public void apply(@NonNull RemoteContext context) { + // nothing + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return indent + toString(); + } + + /** + * Returns the min value + * + * @return minimum value + */ + public float getMin() { + return mV1; + } + + /** + * Returns the max value + * + * @return maximum value + */ + public float getMax() { + return mV2; + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append(indent, "WIDTH_IN = [" + getMin() + ", " + getMax() + "]"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java index b11deae3d196..88449c4a9016 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java @@ -104,6 +104,11 @@ public abstract class DimensionModifierOperation extends Operation } } + /** + * Returns true if the dimension is set using a weight + * + * @return true if using weight, false otherwise + */ public boolean hasWeight() { return mType == Type.WEIGHT; } @@ -136,6 +141,11 @@ public abstract class DimensionModifierOperation extends Operation mOutValue = mValue = value; } + /** + * Returns the serialized name for this operation + * + * @return the serialized name + */ @NonNull public String serializedName() { return "DIMENSION"; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java index 15c2f46093d2..dc5918037946 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java @@ -222,6 +222,26 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param scaleX scaleX of the layer + * @param scaleY scaleY of the layer + * @param rotationX rotationX of the layer + * @param rotationY rotationY of the layer + * @param rotationZ rotationZ of the layer + * @param shadowElevation the shadow elevation + * @param transformOriginX the X origin of the transformations + * @param transformOriginY the Y origin of the transformations + * @param alpha the alpha of the layer + * @param cameraDistance the camera distance + * @param blendMode blending mode of the layer + * @param spotShadowColorId the spot shadow color id + * @param ambientShadowColorId the ambient shadow color id + * @param colorFilterId the color filter id + * @param renderEffectId the render effect id + */ public static void apply( WireBuffer buffer, float scaleX, @@ -257,6 +277,12 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { buffer.writeInt(renderEffectId); } + /** + * Read the operation from the buffer + * + * @param buffer a WireBuffer + * @param operations the list of operations read so far + */ public static void read(WireBuffer buffer, List<Operation> operations) { float scaleX = buffer.readFloat(); float scaleY = buffer.readFloat(); @@ -292,6 +318,11 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { renderEffectId)); } + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the GraphicsLayer Modifier") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java index c19bd2f6b7c0..cc32f2699dfe 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java @@ -19,47 +19,37 @@ import android.annotation.NonNull; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; -import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; -import com.android.internal.widget.remotecompose.core.operations.DrawBase2; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import java.util.List; /** Set the min / max height dimension on a component */ -public class HeightInModifierOperation extends DrawBase2 implements ModifierOperation { +public class HeightInModifierOperation extends DimensionInModifierOperation { private static final int OP_CODE = Operations.MODIFIER_HEIGHT_IN; public static final String CLASS_NAME = "HeightInModifierOperation"; - /** - * Read this operation and add it to the list of operations - * - * @param buffer the buffer to read - * @param operations the list of operations that will be added to - */ - public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { - Maker m = HeightInModifierOperation::new; - read(m, buffer, operations); + public HeightInModifierOperation(float min, float max) { + super(OP_CODE, min, max); } - /** - * Returns the min value - * - * @return minimum value - */ - public float getMin() { - return mV1; + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, getMin(), getMax()); } /** - * Returns the max value + * Read this operation and add it to the list of operations * - * @return maximum value + * @param buffer the buffer to read + * @param operations the list of operations that will be added to */ - public float getMax() { - return mV2; + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + float v1 = buffer.readFloat(); + float v2 = buffer.readFloat(); + operations.add(new HeightInModifierOperation(v1, v2)); } /** @@ -81,11 +71,6 @@ public class HeightInModifierOperation extends DrawBase2 implements ModifierOper return CLASS_NAME; } - @Override - protected void write(@NonNull WireBuffer buffer, float v1, float v2) { - apply(buffer, v1, v2); - } - /** * Populate the documentation with a description of this operation * @@ -98,14 +83,6 @@ public class HeightInModifierOperation extends DrawBase2 implements ModifierOper .field(DocumentedOperation.FLOAT, "max", "The maximum height, -1 if not applied"); } - public HeightInModifierOperation(float min, float max) { - super(min, max); - mName = CLASS_NAME; - } - - @Override - public void paint(@NonNull PaintContext context) {} - /** * Writes out the HeightInModifier to the buffer * @@ -114,7 +91,9 @@ public class HeightInModifierOperation extends DrawBase2 implements ModifierOper * @param y1 start y of the DrawOval */ public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { - write(buffer, OP_CODE, x1, y1); + buffer.start(OP_CODE); + buffer.writeFloat(x1); + buffer.writeFloat(y1); } @Override diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java index 4b50a916b9cd..154740d5536c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java @@ -52,6 +52,13 @@ public class HeightModifierOperation extends DimensionModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param type the type of dimension rule (DimensionModifierOperation.Type) + * @param value the value of the dimension + */ public static void apply(@NonNull WireBuffer buffer, int type, float value) { buffer.start(OP_CODE); buffer.writeInt(type); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java index 2e9d6619d011..09e2228b847a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java @@ -23,6 +23,7 @@ import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.SerializableToString; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation; @@ -32,7 +33,8 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin import java.util.List; /** Capture a host action information. This can be triggered on eg. a click. */ -public class HostActionOperation extends Operation implements ActionOperation { +public class HostActionOperation extends Operation + implements ActionOperation, SerializableToString { private static final int OP_CODE = Operations.HOST_ACTION; int mActionId = -1; @@ -51,6 +53,11 @@ public class HostActionOperation extends Operation implements ActionOperation { return mActionId; } + /** + * Returns the serialized name for this operation + * + * @return the serialized name + */ @NonNull public String serializedName() { return "HOST_ACTION"; @@ -83,6 +90,12 @@ public class HostActionOperation extends Operation implements ActionOperation { context.runAction(mActionId, ""); } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param actionId the action id + */ public static void apply(@NonNull WireBuffer buffer, int actionId) { buffer.start(OP_CODE); buffer.writeInt(actionId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java index 49ef58e0fe53..8a8809c653f8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java @@ -57,6 +57,11 @@ public class HostNamedActionOperation extends Operation implements ActionOperati return "HostNamedActionOperation(" + mTextId + " : " + mValueId + ")"; } + /** + * Name used during serialization + * + * @return the serialized name for this operation + */ @NonNull public String serializedName() { return "HOST_NAMED_ACTION"; @@ -105,6 +110,14 @@ public class HostNamedActionOperation extends Operation implements ActionOperati context.runNamedAction(mTextId, value); } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param textId the text id of the action + * @param type the type of the action + * @param valueId the value id associated with the action + */ public static void apply(@NonNull WireBuffer buffer, int textId, int type, int valueId) { buffer.start(OP_CODE); buffer.writeInt(textId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java index 9588e99a65b6..4ad11d267406 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java @@ -110,6 +110,12 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme mVelocity); } + /** + * Serialize the string + * + * @param indent padding to display + * @param serializer append the string + */ // @Override public void serializeToString(int indent, StringSerializer serializer) { serializer.append(indent, "MARQUEE = [" + mIterations + "]"); @@ -153,14 +159,35 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme return "MarqueeModifierOperation(" + mIterations + ")"; } + /** + * Name of the operation + * + * @return name + */ public static String name() { return CLASS_NAME; } + /** + * id of the operation + * + * @return the operation id + */ public static int id() { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param iterations the number of iterations + * @param animationMode animation mode + * @param repeatDelayMillis repeat delay in ms + * @param initialDelayMillis initial delay before the marquee start in ms + * @param spacing the spacing between marquee + * @param velocity the velocity of the marquee animation + */ public static void apply( WireBuffer buffer, int iterations, @@ -178,6 +205,12 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme buffer.writeFloat(velocity); } + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ public static void read(WireBuffer buffer, List<Operation> operations) { int iterations = buffer.readInt(); int animationMode = buffer.readInt(); @@ -195,6 +228,11 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme velocity)); } + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("specify a Marquee Modifier") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java index f8926fef56fa..a86fb2c1f6a5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java @@ -22,5 +22,11 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin /** Represents a modifier */ public interface ModifierOperation extends OperationInterface { + /** + * Serialize the string + * + * @param indent padding to display + * @param serializer append the string + */ void serializeToString(int indent, @NonNull StringSerializer serializer); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java index 42719478faf0..2cd2728f0720 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java @@ -65,6 +65,12 @@ public class OffsetModifierOperation extends DecoratorModifierOperation { apply(buffer, mX, mY); } + /** + * Serialize the string + * + * @param indent padding to display + * @param serializer append the string + */ // @Override public void serializeToString(int indent, StringSerializer serializer) { serializer.append(indent, "OFFSET = [" + mX + ", " + mY + "]"); @@ -110,18 +116,36 @@ public class OffsetModifierOperation extends DecoratorModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param x x offset + * @param y y offset + */ public static void apply(WireBuffer buffer, float x, float y) { buffer.start(OP_CODE); buffer.writeFloat(x); buffer.writeFloat(y); } + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ public static void read(WireBuffer buffer, List<Operation> operations) { float x = buffer.readFloat(); float y = buffer.readFloat(); operations.add(new OffsetModifierOperation(x, y)); } + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the Offset Modifier") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java index bcfbdd68472f..3225d5c6f92c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java @@ -132,6 +132,15 @@ public class PaddingModifierOperation extends Operation implements ModifierOpera return Operations.MODIFIER_PADDING; } + /** + * Write operation to the buffer + * + * @param buffer a WireBuffer + * @param left left padding + * @param top top padding + * @param right right padding + * @param bottom bottom padding + */ public static void apply( @NonNull WireBuffer buffer, float left, float top, float right, float bottom) { buffer.start(Operations.MODIFIER_PADDING); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java index fe074e4754e2..9787d9b4b399 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java @@ -143,19 +143,40 @@ public class RippleModifierOperation extends DecoratorModifierOperation implemen serializer.append(indent, "RIPPLE_MODIFIER"); } + /** + * The operation name + * + * @return operation name + */ @NonNull public static String name() { return "RippleModifier"; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { operations.add(new RippleModifierOperation()); } + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, name()) .description( diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java index 8950579354b7..76b3373a52d9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java @@ -135,6 +135,12 @@ public class ScrollModifierOperation extends ListActionsOperation apply(buffer, mDirection, mPositionExpression, mMax, mNotchMax); } + /** + * Serialize the string + * + * @param indent padding to display + * @param serializer append the string + */ // @Override public void serializeToString(int indent, StringSerializer serializer) { serializer.append(indent, "SCROLL = [" + mDirection + "]"); @@ -190,6 +196,15 @@ public class ScrollModifierOperation extends ListActionsOperation return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param direction direction of the scroll (HORIZONTAL, VERTICAL) + * @param position the current position + * @param max the maximum position + * @param notchMax the maximum notch + */ public static void apply( WireBuffer buffer, int direction, float position, float max, float notchMax) { buffer.start(OP_CODE); @@ -199,6 +214,12 @@ public class ScrollModifierOperation extends ListActionsOperation buffer.writeFloat(notchMax); } + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ public static void read(WireBuffer buffer, List<Operation> operations) { int direction = buffer.readInt(); float position = buffer.readFloat(); @@ -207,6 +228,11 @@ public class ScrollModifierOperation extends ListActionsOperation operations.add(new ScrollModifierOperation(direction, position, max, notchMax)); } + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define a Scroll Modifier") @@ -300,12 +326,24 @@ public class ScrollModifierOperation extends ListActionsOperation public void onTouchCancel( RemoteContext context, CoreDocument document, Component component, float x, float y) {} + /** + * Set the horizontal scroll dimension + * + * @param hostDimension the horizontal host dimension + * @param contentDimension the horizontal content dimension + */ public void setHorizontalScrollDimension(float hostDimension, float contentDimension) { mHostDimension = hostDimension; mContentDimension = contentDimension; mMaxScrollX = contentDimension - hostDimension; } + /** + * Set the vertical scroll dimension + * + * @param hostDimension the vertical host dimension + * @param contentDimension the vertical content dimension + */ public void setVerticalScrollDimension(float hostDimension, float contentDimension) { mHostDimension = hostDimension; mContentDimension = contentDimension; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java index b6977a035c9e..d625900fcf2e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java @@ -49,6 +49,11 @@ public class ValueFloatChangeActionOperation extends Operation implements Action return "ValueFloatChangeActionOperation(" + mTargetValueId + ")"; } + /** + * The name of the operation used during serialization + * + * @return the operation serialized name + */ public String serializedName() { return "VALUE_FLOAT_CHANGE"; } @@ -76,18 +81,36 @@ public class ValueFloatChangeActionOperation extends Operation implements Action context.overrideFloat(mTargetValueId, mValue); } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param valueId the value id + * @param value the value to set + */ public static void apply(WireBuffer buffer, int valueId, float value) { buffer.start(OP_CODE); buffer.writeInt(valueId); buffer.writeFloat(value); } + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ public static void read(WireBuffer buffer, List<Operation> operations) { int valueId = buffer.readInt(); float value = buffer.readFloat(); operations.add(new ValueFloatChangeActionOperation(valueId, value)); } + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, "ValueFloatChangeActionOperation") .description( diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java index 766271a70ce4..3f26c5e5575b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java @@ -50,6 +50,11 @@ public class ValueFloatExpressionChangeActionOperation extends Operation return "ValueFloatExpressionChangeActionOperation(" + mTargetValueId + ")"; } + /** + * The name of the operation used during serialization + * + * @return the operation serialized name + */ @NonNull public String serializedName() { return "VALUE_FLOAT_EXPRESSION_CHANGE"; @@ -83,6 +88,13 @@ public class ValueFloatExpressionChangeActionOperation extends Operation document.evaluateFloatExpression(mValueExpressionId, mTargetValueId, context); } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param valueId the value id + * @param value the value to set + */ public static void apply(@NonNull WireBuffer buffer, int valueId, int value) { buffer.start(OP_CODE); buffer.writeInt(valueId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java index 60166a7b2102..8c5bb6fdb268 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java @@ -49,6 +49,11 @@ public class ValueIntegerChangeActionOperation extends Operation implements Acti return "ValueChangeActionOperation(" + mTargetValueId + ")"; } + /** + * The name of the operation used during serialization + * + * @return the operation serialized name + */ @NonNull public String serializedName() { return "VALUE_INTEGER_CHANGE"; @@ -81,6 +86,13 @@ public class ValueIntegerChangeActionOperation extends Operation implements Acti context.overrideInteger(mTargetValueId, mValue); } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param valueId the value id + * @param value the value to set + */ public static void apply(@NonNull WireBuffer buffer, int valueId, int value) { buffer.start(OP_CODE); buffer.writeInt(valueId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java index 502508058465..00c80f12aaba 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java @@ -50,6 +50,11 @@ public class ValueIntegerExpressionChangeActionOperation extends Operation return "ValueIntegerExpressionChangeActionOperation(" + mTargetValueId + ")"; } + /** + * The name of the operation used during serialization + * + * @return the operation serialized name + */ @NonNull public String serializedName() { return "VALUE_INTEGER_EXPRESSION_CHANGE"; @@ -83,6 +88,13 @@ public class ValueIntegerExpressionChangeActionOperation extends Operation document.evaluateIntExpression(mValueExpressionId, (int) mTargetValueId, context); } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param valueId the long id pointing to an int value + * @param value the value to set (long id)` + */ public static void apply(@NonNull WireBuffer buffer, long valueId, long value) { buffer.start(OP_CODE); buffer.writeLong(valueId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java index 8093bb3c64ec..57e30d4126ba 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java @@ -53,6 +53,11 @@ public class ValueStringChangeActionOperation extends Operation implements Actio return mTargetValueId; } + /** + * The name of the operation used during serialization + * + * @return the operation serialized name + */ @NonNull public String serializedName() { return "VALUE_CHANGE"; @@ -85,6 +90,13 @@ public class ValueStringChangeActionOperation extends Operation implements Actio context.overrideText(mTargetValueId, mValueId); } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param valueId the string id + * @param value the value to set (string id)` + */ public static void apply(@NonNull WireBuffer buffer, int valueId, int value) { buffer.start(OP_CODE); buffer.writeInt(valueId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java index c3624e5b3d88..8c1ffbd2c500 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java @@ -19,47 +19,37 @@ import android.annotation.NonNull; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; -import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; -import com.android.internal.widget.remotecompose.core.operations.DrawBase2; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import java.util.List; /** Set the min / max width dimension on a component */ -public class WidthInModifierOperation extends DrawBase2 implements ModifierOperation { +public class WidthInModifierOperation extends DimensionInModifierOperation { private static final int OP_CODE = Operations.MODIFIER_WIDTH_IN; public static final String CLASS_NAME = "WidthInModifierOperation"; - /** - * Read this operation and add it to the list of operations - * - * @param buffer the buffer to read - * @param operations the list of operations that will be added to - */ - public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { - Maker m = WidthInModifierOperation::new; - read(m, buffer, operations); + public WidthInModifierOperation(float min, float max) { + super(OP_CODE, min, max); } - /** - * Returns the min value - * - * @return minimum value - */ - public float getMin() { - return mV1; + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, getMin(), getMax()); } /** - * Returns the max value + * Read this operation and add it to the list of operations * - * @return maximum value + * @param buffer the buffer to read + * @param operations the list of operations that will be added to */ - public float getMax() { - return mV2; + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + float v1 = buffer.readFloat(); + float v2 = buffer.readFloat(); + operations.add(new WidthInModifierOperation(v1, v2)); } /** @@ -81,11 +71,6 @@ public class WidthInModifierOperation extends DrawBase2 implements ModifierOpera return CLASS_NAME; } - @Override - protected void write(@NonNull WireBuffer buffer, float v1, float v2) { - apply(buffer, v1, v2); - } - /** * Populate the documentation with a description of this operation * @@ -98,14 +83,6 @@ public class WidthInModifierOperation extends DrawBase2 implements ModifierOpera .field(DocumentedOperation.FLOAT, "max", "The maximum width, -1 if not applied"); } - public WidthInModifierOperation(float min, float max) { - super(min, max); - mName = CLASS_NAME; - } - - @Override - public void paint(@NonNull PaintContext context) {} - /** * Writes out the WidthInModifier to the buffer * @@ -114,7 +91,9 @@ public class WidthInModifierOperation extends DrawBase2 implements ModifierOpera * @param y1 start y of the DrawOval */ public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { - write(buffer, OP_CODE, x1, y1); + buffer.start(OP_CODE); + buffer.writeFloat(x1); + buffer.writeFloat(y1); } @Override diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java index 532027ab2087..687238e62bac 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java @@ -52,6 +52,13 @@ public class WidthModifierOperation extends DimensionModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param type the type of dimension rule (DimensionModifierOperation.Type) + * @param value the value of the dimension + */ public static void apply(@NonNull WireBuffer buffer, int type, float value) { buffer.start(OP_CODE); buffer.writeInt(type); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java index 35de33a9997a..52841a7e6779 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java @@ -55,6 +55,12 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation { apply(buffer, mValue); } + /** + * Serialize the string + * + * @param indent padding to display + * @param serializer append the string + */ // @Override public void serializeToString(int indent, StringSerializer serializer) { serializer.append(indent, "ZINDEX = [" + mValue + "]"); @@ -99,16 +105,33 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation { return OP_CODE; } + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param value the z-index value + */ public static void apply(WireBuffer buffer, float value) { buffer.start(OP_CODE); buffer.writeFloat(value); } + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ public static void read(WireBuffer buffer, List<Operation> operations) { float value = buffer.readFloat(); operations.add(new ZIndexModifierOperation(value)); } + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the Z-Index Modifier") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java index 95434696abdc..4c7f503e0bf8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java @@ -682,9 +682,9 @@ public class PaintBundle { * @param radius Must be positive. The radius of the gradient. * @param colors The sRGB colors distributed between the center and edge * @param stops May be <code>null</code>. Valid values are between <code>0.0f</code> and <code> - * 1.0f</code>. The relative position of each corresponding color in the colors array. If - * <code>null</code>, colors are distributed evenly between the center and edge of the - * circle. + * 1.0f</code>. The relative position of each corresponding color in the colors + * array. If <code>null</code>, colors are distributed evenly between the center and edge of + * the circle. * @param tileMode The Shader tiling mode */ public void setRadialGradient( @@ -808,7 +808,7 @@ public class PaintBundle { } /** - * Set the color based the R,G,B,A values + * Set the color based the R,G,B,A values (Warning this does not support NaN ids) * * @param r red (0.0 to 1.0) * @param g green (0.0 to 1.0) @@ -816,7 +816,7 @@ public class PaintBundle { * @param a alpha (0.0 to 1.0) */ public void setColor(float r, float g, float b, float a) { - setColor((int) (r * 255), (int) (g * 255), (int) (b * 255), (int) (a * 255)); + setColor(Utils.toARGB(a, r, g, b)); } /** @@ -897,6 +897,11 @@ public class PaintBundle { mPos++; } + /** + * set Filter Bitmap + * + * @param filter set to false to disable interpolation + */ public void setFilterBitmap(boolean filter) { mArray[mPos] = FILTER_BITMAP | (filter ? (1 << 16) : 0); mPos++; @@ -944,6 +949,12 @@ public class PaintBundle { } } + /** + * Convert a blend mode integer as a string + * + * @param mode the blend mode + * @return the blend mode as a string + */ public static @NonNull String blendModeString(int mode) { switch (mode) { case PaintBundle.BLEND_MODE_CLEAR: diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java index ff6f45db5385..2812eed8a5ab 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java @@ -17,51 +17,250 @@ package com.android.internal.widget.remotecompose.core.operations.paint; import android.annotation.NonNull; +import java.util.Locale; + // TODO: this interface is unused. Delete it. public interface TextPaint { + + /** + * Helper to setColor(), that takes a,r,g,b and constructs the color int + * + * @param a The new alpha component (0..255) of the paint's color. + * @param r The new red component (0..255) of the paint's color. + * @param g The new green component (0..255) of the paint's color. + * @param b The new blue component (0..255) of the paint's color. + */ void setARGB(int a, int r, int g, int b); + /** + * Helper for setFlags(), setting or clearing the DITHER_FLAG bit Dithering affects how colors + * that are higher precision than the device are down-sampled. No dithering is generally faster, + * but higher precision colors are just truncated down (e.g. 8888 -> 565). Dithering tries to + * distribute the error inherent in this process, to reduce the visual artifacts. + * + * @param dither true to set the dithering bit in flags, false to clear it + */ void setDither(boolean dither); + /** + * Set the paint's elegant height metrics flag. This setting selects font variants that have not + * been compacted to fit Latin-based vertical metrics, and also increases top and bottom bounds + * to provide more space. + * + * @param elegant set the paint's elegant metrics flag for drawing text. + */ void setElegantTextHeight(boolean elegant); + /** + * Set a end hyphen edit on the paint. + * + * <p>By setting end hyphen edit, the measurement and drawing is performed with modifying + * hyphenation at the end of line. For example, by passing character is appended at the end of + * line. + * + * <pre> + * <code> + * Paint paint = new Paint(); + * paint.setEndHyphenEdit(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN); + * paint.measureText("abc", 0, 3); // Returns the width of "abc-" + * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "abc-" + * </code> + * </pre> + * + * @param endHyphen a end hyphen edit value. + */ void setEndHyphenEdit(int endHyphen); + /** + * Helper for setFlags(), setting or clearing the FAKE_BOLD_TEXT_FLAG bit + * + * @param fakeBoldText true to set the fakeBoldText bit in the paint's flags, false to clear it. + */ void setFakeBoldText(boolean fakeBoldText); + /** + * Set the paint's flags. Use the Flag enum to specific flag values. + * + * @param flags The new flag bits for the paint + */ void setFlags(int flags); + /** + * Set font feature settings. + * + * <p>The format is the same as the CSS font-feature-settings attribute: <a + * href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> + * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> + * + * @param settings the font feature settings string to use, may be null. + */ void setFontFeatureSettings(@NonNull String settings); + /** + * Set the paint's hinting mode. May be either + * + * @param mode The new hinting mode. (HINTING_OFF or HINTING_ON) + */ void setHinting(int mode); + /** + * Set the paint's letter-spacing for text. The default value is 0. The value is in 'EM' units. + * Typical values for slight expansion will be around 0.05. Negative values tighten text. + * + * @param letterSpacing set the paint's letter-spacing for drawing text. + */ void setLetterSpacing(float letterSpacing); + /** + * Helper for setFlags(), setting or clearing the LINEAR_TEXT_FLAG bit + * + * @param linearText true to set the linearText bit in the paint's flags, false to clear it. + */ void setLinearText(boolean linearText); + /** + * This draws a shadow layer below the main layer, with the specified offset and color, and blur + * radius. If radius is 0, then the shadow layer is removed. + * + * <p>Can be used to create a blurred shadow underneath text. Support for use with other drawing + * operations is constrained to the software rendering pipeline. + * + * <p>The alpha of the shadow will be the paint's alpha if the shadow color is opaque, or the + * alpha from the shadow color if not. + * + * @param radius the radius of the shadows + * @param dx the x offset of the shadow + * @param dy the y offset of the shadow + * @param shadowColor the color of the shadow + */ void setShadowLayer(float radius, float dx, float dy, int shadowColor); + /** + * Set a start hyphen edit on the paint. + * + * <p>By setting start hyphen edit, the measurement and drawing is performed with modifying + * hyphenation at the start of line. For example, by passing character is appended at the start + * of line. + * + * <pre> + * <code> + * Paint paint = new Paint(); + * paint.setStartHyphenEdit(Paint.START_HYPHEN_EDIT_INSERT_HYPHEN); + * paint.measureText("abc", 0, 3); // Returns the width of "-abc" + * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "-abc" + * </code> + * </pre> + * + * The default value is 0 which is equivalent to + * + * @param startHyphen a start hyphen edit value. + */ void setStartHyphenEdit(int startHyphen); + /** + * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit + * + * @param strikeThruText true to set the strikeThruText bit in the paint's flags, false to clear + * it. + */ void setStrikeThruText(boolean strikeThruText); + /** + * Set the paint's Cap. + * + * @param cap set the paint's line cap style, used whenever the paint's style is Stroke or + * StrokeAndFill. + */ void setStrokeCap(int cap); + /** + * Helper for setFlags(), setting or clearing the SUBPIXEL_TEXT_FLAG bit + * + * @param subpixelText true to set the subpixelText bit in the paint's flags, false to clear it. + */ void setSubpixelText(boolean subpixelText); + /** + * Set the paint's text alignment. This controls how the text is positioned relative to its + * origin. LEFT align means that all of the text will be drawn to the right of its origin (i.e. + * the origin specifies the LEFT edge of the text) and so on. + * + * @param align set the paint's Align value for drawing text. + */ void setTextAlign(int align); + /** + * Set the text locale list to a one-member list consisting of just the locale. + * + * @param locale the paint's locale value for drawing text, must not be null. + */ void setTextLocale(int locale); + /** + * Set the text locale list. + * + * <p>The text locale list affects how the text is drawn for some languages. + * + * <p>For example, if the locale list contains {@link Locale#CHINESE} or {@link Locale#CHINA}, + * then the text renderer will prefer to draw text using a Chinese font. Likewise, if the locale + * list contains {@link Locale#JAPANESE} or {@link Locale#JAPAN}, then the text renderer will + * prefer to draw text using a Japanese font. If the locale list contains both, the order those + * locales appear in the list is considered for deciding the font. + * + * <p>This distinction is important because Chinese and Japanese text both use many of the same + * Unicode code points but their appearance is subtly different for each language. + * + * <p>By default, the text locale list is initialized to a one-member list just containing the + * system locales. This assumes that the text to be rendered will most likely be in the user's + * preferred language. + * + * <p>If the actual language or languages of the text is/are known, then they can be provided to + * the text renderer using this method. The text renderer may attempt to guess the language + * script based on the contents of the text to be drawn independent of the text locale here. + * Specifying the text locales just helps it do a better job in certain ambiguous cases. + * + * @param localesArray the paint's locale list for drawing text, must not be null or empty. + */ void setTextLocales(int localesArray); + /** + * Set the paint's horizontal scale factor for text. The default value is 1.0. Values > 1.0 will + * stretch the text wider. Values < 1.0 will stretch the text narrower. + * + * @param scaleX set the paint's scale in X for drawing/measuring text. + */ void setTextScaleX(float scaleX); + /** + * Set the paint's text size. This value must be > 0 + * + * @param textSize set the paint's text size in pixel units. + */ void setTextSize(float textSize); + /** + * Set the paint's horizontal skew factor for text. The default value is 0. For approximating + * oblique text, use values around -0.25. + * + * @param skewX set the paint's skew factor in X for drawing text. + */ void setTextSkewX(float skewX); + /** + * Helper for setFlags(), setting or clearing the UNDERLINE_TEXT_FLAG bit + * + * @param underlineText true to set the underlineText bit in the paint's flags, false to clear + * it. + */ void setUnderlineText(boolean underlineText); + /** + * Set the paint's extra word-spacing for text. + * + * <p>Increases the white space width between words with the given amount of pixels. The default + * value is 0. + * + * @param wordSpacing set the paint's extra word-spacing for drawing text in pixels. + */ void setWordSpacing(float wordSpacing); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java index b92f96f63eb9..704f6ce3447a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java @@ -22,6 +22,14 @@ import android.annotation.Nullable; * unavailable */ public interface CollectionsAccess { + + /** + * Get the float value in the array at the given index + * + * @param id the id of the float array + * @param index the index of the value + * @return + */ float getFloatValue(int id, int index); /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java index 07a3d8482db2..04beba3c4af8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java @@ -28,6 +28,12 @@ public class DataMap { mIds = ids; } + /** + * Return position for given string + * + * @param str string + * @return position associated with the string + */ public int getPos(@NonNull String str) { for (int i = 0; i < mNames.length; i++) { String name = mNames[i]; @@ -38,10 +44,22 @@ public class DataMap { return -1; } + /** + * Return type for given index + * + * @param pos index + * @return type at index + */ public byte getType(int pos) { return mTypes[pos]; } + /** + * Return id for given index + * + * @param pos index + * @return id at index + */ public int getId(int pos) { return mIds[pos]; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java index 98ee91b370e0..93af8bcef206 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java @@ -76,6 +76,20 @@ public class ImageScaling { adjustDrawToType(); } + /** + * Setup the ImageScaling + * + * @param srcLeft src left + * @param srcTop src top + * @param srcRight src right + * @param srcBottom src bottom + * @param dstLeft destination left + * @param dstTop destination top + * @param dstRight destination right + * @param dstBottom destination bottom + * @param type type of scaling + * @param scale scale factor + */ public void setup( float srcLeft, float srcTop, @@ -215,6 +229,12 @@ public class ImageScaling { } } + /** + * Utility to map a string to the given type + * + * @param type + * @return + */ @NonNull public static String typeToString(int type) { String[] typeString = { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java index b9aa88146f2a..257eb0645938 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java @@ -39,12 +39,20 @@ public class IntMap<T> { } } + /** Clear the map */ public void clear() { Arrays.fill(mKeys, NOT_PRESENT); mValues.clear(); mSize = 0; } + /** + * Insert the value into the map with the given key + * + * @param key + * @param value + * @return + */ @Nullable public T put(int key, @NonNull T value) { if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT"); @@ -54,6 +62,12 @@ public class IntMap<T> { return insert(key, value); } + /** + * Return the value associated with the given key + * + * @param key + * @return + */ @Nullable public T get(int key) { int index = findKey(key); @@ -62,6 +76,11 @@ public class IntMap<T> { } else return mValues.get(index); } + /** + * Return the size of the map + * + * @return + */ public int size() { return mSize; } @@ -117,6 +136,12 @@ public class IntMap<T> { } } + /** + * Remote the key from the map + * + * @param key + * @return + */ @Nullable public T remove(int key) { int index = hash(key) % mKeys.length; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java index 0616cc7306f5..1f98f62a20b3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java @@ -41,18 +41,42 @@ public class NanMap { public static final float CLOSE_NAN = Utils.asNan(CLOSE); public static final float DONE_NAN = Utils.asNan(DONE); + /** + * Returns true if the float id is a system variable + * + * @param value the id encoded as float NaN + * @return + */ public static boolean isSystemVariable(float value) { return (fromNaN(value) >> 20) == 0; } + /** + * Returns true if the float id is a normal variable + * + * @param value the id encoded as float NaN + * @return + */ public static boolean isNormalVariable(float value) { return (fromNaN(value) >> 20) == 1; } + /** + * Returns true if the float id is a data variable + * + * @param value the id encoded as float NaN + * @return + */ public static boolean isDataVariable(float value) { return (fromNaN(value) >> 20) == 2; } + /** + * Returns true if the float id is an operation variable + * + * @param value the id encoded as float NaN + * @return + */ public static boolean isOperationVariable(float value) { return (fromNaN(value) >> 20) == 3; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java index cc6c2a6ac7a9..928e9ea1a280 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java @@ -134,6 +134,12 @@ public interface AccessibleComponent extends AccessibilitySemantics { return mDescription; } + /** + * Map int value to Role enum value + * + * @param i int value + * @return corresponding enum value + */ public static Role fromInt(int i) { if (i < UNKNOWN.ordinal()) { return Role.values()[i]; diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java new file mode 100644 index 000000000000..2be8057ce097 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.serialize; + +import android.annotation.Nullable; + +import java.util.List; +import java.util.Map; + +/** Represents a serializer for a map */ +public interface MapSerializer { + + /** + * Add a list entry to this map. The List values can be any primitive, List, Map, or + * Serializable + * + * @param key The key + * @param value The list + */ + <T> void add(String key, @Nullable List<T> value); + + /** + * Add a map entry to this map. The map values can be any primitive, List, Map, or Serializable + * + * @param key The key + * @param value The list + */ + <T> void add(String key, @Nullable Map<String, T> value); + + /** + * Adds any Serializable type to this map + * + * @param key The key + * @param value The Serializable + */ + void add(String key, @Nullable Serializable value); + + /** + * Adds a String entry + * + * @param key The key + * @param value The String + */ + void add(String key, @Nullable String value); + + /** + * Adds a Byte entry + * + * @param key The key + * @param value The Byte + */ + void add(String key, @Nullable Byte value); + + /** + * Adds a Short entry + * + * @param key The key + * @param value The Short + */ + void add(String key, @Nullable Short value); + + /** + * Adds an Integer entry + * + * @param key The key + * @param value The Integer + */ + void add(String key, @Nullable Integer value); + + /** + * Adds a Long entry + * + * @param key The key + * @param value The Long + */ + void add(String key, @Nullable Long value); + + /** + * Adds a Float entry + * + * @param key The key + * @param value The Float + */ + void add(String key, @Nullable Float value); + + /** + * Adds a Double entry + * + * @param key The key + * @param value The Double + */ + void add(String key, @Nullable Double value); + + /** + * Adds a Boolean entry + * + * @param key The key + * @param value The Boolean + */ + void add(String key, @Nullable Boolean value); + + /** + * Adds a Enum entry + * + * @param key The key + * @param value The Enum + */ + <T extends Enum<T>> void add(String key, @Nullable Enum<T> value); +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/Serializable.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/Serializable.java new file mode 100644 index 000000000000..820cdccfeabb --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/Serializable.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.serialize; + +/** Implementation for any class that wants to serialize itself */ +public interface Serializable { + + /** + * Called when this class is to be serialized + * + * @param serializer Interface to the serializer + */ + void serialize(MapSerializer serializer); +} diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index b17e3dc82d50..77f4b6a83eef 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -39,12 +39,13 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.remotecompose.accessibility.RemoteComposeTouchHelper; import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.RemoteContextAware; import com.android.internal.widget.remotecompose.core.operations.NamedVariable; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas; /** A view to to display and play RemoteCompose documents */ -public class RemoteComposePlayer extends FrameLayout { +public class RemoteComposePlayer extends FrameLayout implements RemoteContextAware { private RemoteComposeCanvas mInner; private static final int MAX_SUPPORTED_MAJOR_VERSION = MAJOR_VERSION; @@ -65,6 +66,11 @@ public class RemoteComposePlayer extends FrameLayout { init(context, attrs, defStyleAttr); } + @Override + public RemoteContext getRemoteContext() { + return mInner.getRemoteContext(); + } + /** * @inheritDoc */ diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java index 9d385ddafe19..14349b028819 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java @@ -185,7 +185,7 @@ public class AndroidRemoteContext extends RemoteContext { @Override public void runAction(int id, @NonNull String metadata) { - mDocument.performClick(id); + mDocument.performClick(this, id); } @Override diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java index 334ba62636ff..f76794fc0372 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java @@ -147,7 +147,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta param.leftMargin = (int) area.getLeft(); param.topMargin = (int) area.getTop(); viewArea.setOnClickListener( - view1 -> mDocument.getDocument().performClick(area.getId())); + view1 -> mDocument.getDocument().performClick(mARContext, area.getId())); addView(viewArea, param); } if (!clickAreas.isEmpty()) { @@ -303,6 +303,10 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mARContext.setUseChoreographer(value); } + public RemoteContext getRemoteContext() { + return mARContext; + } + public interface ClickCallbacks { void click(int id, String metadata); } diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java index 41432294b3c2..9b97c8feaf12 100644 --- a/core/tests/coretests/src/android/app/NotificationManagerTest.java +++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java @@ -19,7 +19,6 @@ package android.app; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; @@ -304,9 +303,8 @@ public class NotificationManagerTest { // It doesn't matter what the returned contents are, as long as we return a channel. // This setup must set up getNotificationChannels(), as that's the method called. - when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(), - anyInt(), anyBoolean())).thenReturn( - new ParceledListSlice<>(List.of(exampleChannel()))); + when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), + anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel()))); // ask for the same channel 100 times without invalidating the cache for (int i = 0; i < 100; i++) { @@ -318,7 +316,7 @@ public class NotificationManagerTest { NotificationChannel unused = mNotificationManager.getNotificationChannel("id"); verify(mNotificationManager.mBackendService, times(2)) - .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean()); + .getNotificationChannels(any(), any(), anyInt()); } @Test @@ -331,24 +329,23 @@ public class NotificationManagerTest { NotificationChannel c2 = new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_NONE); - when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(), - anyInt(), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(c1, c2))); + when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), + anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, c2))); assertThat(mNotificationManager.getNotificationChannel("id1")).isEqualTo(c1); assertThat(mNotificationManager.getNotificationChannel("id2")).isEqualTo(c2); assertThat(mNotificationManager.getNotificationChannel("id3")).isNull(); verify(mNotificationManager.mBackendService, times(1)) - .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean()); + .getNotificationChannels(any(), any(), anyInt()); } @Test @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS) public void getNotificationChannels_cachedUntilInvalidated() throws Exception { NotificationManager.invalidateNotificationChannelCache(); - when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(), - anyInt(), anyBoolean())).thenReturn( - new ParceledListSlice<>(List.of(exampleChannel()))); + when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), + anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel()))); // ask for channels 100 times without invalidating the cache for (int i = 0; i < 100; i++) { @@ -360,7 +357,7 @@ public class NotificationManagerTest { List<NotificationChannel> res = mNotificationManager.getNotificationChannels(); verify(mNotificationManager.mBackendService, times(2)) - .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean()); + .getNotificationChannels(any(), any(), anyInt()); assertThat(res).containsExactlyElementsIn(List.of(exampleChannel())); } @@ -378,9 +375,8 @@ public class NotificationManagerTest { NotificationChannel c2 = new NotificationChannel("other", "name2", NotificationManager.IMPORTANCE_DEFAULT); - when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(), - anyInt(), anyBoolean())).thenReturn( - new ParceledListSlice<>(List.of(c1, conv1, c2))); + when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), + anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, conv1, c2))); // Lookup for channel c1 and c2: returned as expected assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(c1); @@ -397,9 +393,9 @@ public class NotificationManagerTest { // Lookup of a nonexistent channel is null assertThat(mNotificationManager.getNotificationChannel("id3")).isNull(); - // All of that should have been one call to getOrCreateNotificationChannels() + // All of that should have been one call to getNotificationChannels() verify(mNotificationManager.mBackendService, times(1)) - .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean()); + .getNotificationChannels(any(), any(), anyInt()); } @Test @@ -419,12 +415,12 @@ public class NotificationManagerTest { NotificationChannel channel3 = channel1.copy(); channel3.setName("name3"); - when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1), - eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel1))); - when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg2), - eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel2))); - when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1), - eq(userId1), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel3))); + when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1), + eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel1))); + when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg2), + eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel2))); + when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1), + eq(userId1))).thenReturn(new ParceledListSlice<>(List.of(channel3))); // set our context to pretend to be from package 1 and userId 0 mContext.setParameters(pkg1, pkg1, userId); @@ -440,7 +436,7 @@ public class NotificationManagerTest { // Those should have been three different calls verify(mNotificationManager.mBackendService, times(3)) - .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean()); + .getNotificationChannels(any(), any(), anyInt()); } @Test diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index cef6970ba25a..c40137f1bd34 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1054,8 +1054,7 @@ public class ViewRootImplTest { ViewRootImpl viewRootImpl = mView.getViewRootImpl(); sInstrumentation.runOnMainSync(() -> { mView.invalidate(); - viewRootImpl.notifyInsetsAnimationRunningStateChanged(true, 0 /* animationType */, - 0 /* insetsTypes */ /* areOtherAnimationsRunning */); + viewRootImpl.notifyInsetsAnimationRunningStateChanged(true); mView.invalidate(); }); sInstrumentation.waitForIdleSync(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index a65e69eee5fe..c40a276cb7bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -56,6 +56,7 @@ import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.shared.bubbles.BubbleInfo; import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage; import com.android.wm.shell.taskview.TaskView; @@ -1093,7 +1094,7 @@ public class Bubble implements BubbleViewProvider { * intent for an app. In this case we don't show a badge on the icon. */ public boolean isAppLaunchIntent() { - if (Flags.enableBubbleAnything() && mAppIntent != null) { + if (BubbleAnythingFlagHelper.enableCreateAnyBubble() && mAppIntent != null) { return mAppIntent.hasCategory("android.intent.category.LAUNCHER"); } return false; 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 5f2b95f7b137..5cd04b11bbfd 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 @@ -1433,7 +1433,7 @@ public class BubbleController implements ConfigurationChangeListener, * @param info the shortcut info for the bubble. */ public void expandStackAndSelectBubble(ShortcutInfo info) { - if (!Flags.enableBubbleAnything()) return; + if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info); if (b.isInflated()) { @@ -1450,7 +1450,7 @@ public class BubbleController implements ConfigurationChangeListener, * @param intent the intent for the bubble. */ public void expandStackAndSelectBubble(Intent intent, UserHandle user) { - if (!Flags.enableBubbleAnything()) return; + if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent); if (b.isInflated()) { @@ -2516,7 +2516,7 @@ public class BubbleController implements ConfigurationChangeListener, * @param entry the entry to bubble. */ static boolean canLaunchInTaskView(Context context, BubbleEntry entry) { - if (Flags.enableBubbleAnything()) return true; + if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) return true; PendingIntent intent = entry.getBubbleMetadata() != null ? entry.getBubbleMetadata().getIntent() : null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 13f8e9ef9dd3..e98d53e85b94 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -71,6 +71,7 @@ import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.shared.TriangleShape; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.taskview.TaskView; import java.io.PrintWriter; @@ -226,7 +227,8 @@ public class BubbleExpandedView extends LinearLayout { MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() - || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); + || (mBubble.getShortcutInfo() != null + && BubbleAnythingFlagHelper.enableCreateAnyBubble())); if (mBubble.isAppBubble()) { Context context = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index 62995319db80..086c91985ae3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -137,14 +137,15 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl // Update bitmap val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset) val drawable = AdaptiveIconDrawable(ColorDrawable(colorAccent), fg) - bitmap = iconFactory.createBadgedIconBitmap(drawable).icon + val bubbleBitmapScale = FloatArray(1) + bitmap = iconFactory.getBubbleBitmap(drawable, bubbleBitmapScale) // Update dot path dotPath = PathParser.createPathFromPathData( res.getString(com.android.internal.R.string.config_icon_mask) ) - val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable) + val scale = bubbleBitmapScale[0] val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f val matrix = Matrix() matrix.setScale( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index ae84f449c0e4..a6b858500dcb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -36,7 +36,7 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; import com.android.internal.protolog.ProtoLog; -import com.android.wm.shell.Flags; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.taskview.TaskView; /** @@ -108,7 +108,8 @@ public class BubbleTaskViewHelper { options.setPendingIntentBackgroundActivityStartMode( MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() - || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); + || (mBubble.getShortcutInfo() != null + && BubbleAnythingFlagHelper.enableCreateAnyBubble())); if (mBubble.getPreparingTransition() != null) { mBubble.getPreparingTransition().surfaceCreated(); } else if (mBubble.isAppBubble()) { 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 4bbe22a59315..6657c9e4b9a9 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.dagger; +import static android.window.DesktopModeFlags.ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY; @@ -1023,7 +1024,7 @@ public abstract class WMShellModule { DesktopModeCompatPolicy desktopModeCompatPolicy) { if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() - || !Flags.enableDesktopSystemDialogsTransitions()) { + || !ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS.isTrue()) { return Optional.empty(); } return Optional.of( 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 ca71cf303a1c..5b206dedee49 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 @@ -56,6 +56,7 @@ import android.view.WindowManager.TRANSIT_TO_FRONT import android.widget.Toast import android.window.DesktopModeFlags import android.window.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE +import android.window.DesktopModeFlags.ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import android.window.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS import android.window.RemoteTransition @@ -1515,7 +1516,7 @@ class DesktopTasksController( private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) { logV("addWallpaperActivity") - if (Flags.enableDesktopWallpaperActivityForSystemUser()) { + if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) { val intent = Intent(context, DesktopWallpaperActivity::class.java) if ( desktopWallpaperActivityTokenProvider.getToken(displayId) == null && @@ -1578,7 +1579,7 @@ class DesktopTasksController( private fun removeWallpaperActivity(wct: WindowContainerTransaction, displayId: Int) { desktopWallpaperActivityTokenProvider.getToken(displayId)?.let { token -> logV("removeWallpaperActivity") - if (Flags.enableDesktopWallpaperActivityForSystemUser()) { + if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) { wct.reorder(token, /* onTop= */ false) } else { wct.removeTask(token) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 14c8429766cc..b3648699ed0b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -27,6 +27,7 @@ import android.view.WindowManager.TRANSIT_PIP import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DesktopModeFlags +import android.window.DesktopModeFlags.ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import android.window.TransitionInfo import android.window.WindowContainerTransaction @@ -275,7 +276,7 @@ class DesktopTasksTransitionObserver( desktopWallpaperActivityTokenProvider .getToken(lastSeenTransitionToCloseWallpaper.displayId) ?.let { wallpaperActivityToken -> - if (Flags.enableDesktopWallpaperActivityForSystemUser()) { + if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) { transitions.startTransition( TRANSIT_TO_BACK, WindowContainerTransaction() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt index 13576aa42737..a5ba6612bb1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt @@ -21,9 +21,9 @@ import android.content.Context import android.content.pm.UserInfo import android.os.UserManager import android.util.SparseArray +import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM import androidx.core.util.forEach import com.android.internal.protolog.ProtoLog -import com.android.window.flags.Flags import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -68,7 +68,7 @@ class DesktopUserRepositories( if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(::onInit, this) } - if (Flags.enableDesktopWindowingHsum()) { + if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) { userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id } } } @@ -80,7 +80,7 @@ class DesktopUserRepositories( /** Returns [DesktopRepository] for the parent user id. */ fun getProfile(profileId: Int): DesktopRepository { - if (Flags.enableDesktopWindowingHsum()) { + if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) { for ((uid, profileIds) in userIdToProfileIdsMap) { if (profileId in profileIds) { return desktopRepoByUserId.getOrCreate(uid) @@ -101,14 +101,14 @@ class DesktopUserRepositories( override fun onUserChanged(newUserId: Int, userContext: Context) { logD("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId) userId = newUserId - if (Flags.enableDesktopWindowingHsum()) { + if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) { sanitizeUsers() } } override fun onUserProfilesChanged(profiles: MutableList<UserInfo>) { logD("onUserProfilesChanged profiles=%s", profiles.toString()) - if (Flags.enableDesktopWindowingHsum()) { + if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) { // TODO(b/366397912): Remove all persisted profile data when the profile changes. userIdToProfileIdsMap[userId] = profiles.map { it.id } } 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 2cf574158358..7e7a793298e2 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 @@ -466,7 +466,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, new DesktopModeOnTaskResizeAnimationListener()); mDesktopTasksController.setOnTaskRepositionAnimationListener( new DesktopModeOnTaskRepositionAnimationListener()); - if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) { + if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) { mRecentsTransitionHandler.addTransitionStateListener( new DesktopModeRecentsTransitionStateListener()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index e5c989ed5f97..053850480ecc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -49,6 +49,7 @@ import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper import com.android.wm.shell.shared.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer @@ -645,7 +646,7 @@ class HandleMenu( private fun bindWindowingPill(style: MenuStyle) { windowingPill.background.setTint(style.backgroundColor) - if (!com.android.wm.shell.Flags.enableBubbleAnything()) { + if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { floatingBtn.visibility = View.GONE } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt index 70c0b54462e3..22bc9782170b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt @@ -5,6 +5,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.graphics.PointF import android.graphics.Rect +import android.view.Choreographer import android.view.MotionEvent import android.view.SurfaceControl import android.view.VelocityTracker @@ -48,7 +49,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor( t.setScale(taskSurface, scale, scale) .setCornerRadius(taskSurface, cornerRadius) .setScale(taskSurface, scale, scale) - .setCornerRadius(taskSurface, cornerRadius) + .setFrameTimeline(Choreographer.getInstance().vsyncId) .setPosition(taskSurface, position.x, position.y) .apply() } @@ -96,6 +97,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor( setTaskPosition(ev.rawX, ev.rawY) val t = transactionFactory() t.setPosition(taskSurface, position.x, position.y) + t.setFrameTimeline(Choreographer.getInstance().vsyncId) t.apply() } diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml index 834814ccbc6b..1c6b1ebd1535 100644 --- a/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml +++ b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml @@ -18,7 +18,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingStart="?android:attr/listPreferredItemPaddingStart"> + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:filterTouchesWhenObscured="true"> <com.android.settingslib.widget.CollapsableTextView android:id="@+id/collapsable_text_view" diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java index e3d7902f34b2..00973811dbf0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java @@ -84,7 +84,11 @@ public final class ActionDisabledByAdminControllerFactory { return false; } DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); - return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, + final SupervisionManager sm = + android.app.supervision.flags.Flags.deprecateDpmSupervisionApis() + ? context.getSystemService(SupervisionManager.class) + : null; + return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, sm, BiometricAuthenticator.TYPE_ANY_BIOMETRIC, new UserHandle(UserHandle.myUserId())); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index c47bf628002d..7c588b3834a5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -4072,7 +4072,7 @@ public class SettingsProvider extends ContentProvider { @VisibleForTesting final class UpgradeController { - private static final int SETTINGS_VERSION = 226; + private static final int SETTINGS_VERSION = 227; private final int mUserId; @@ -6266,6 +6266,51 @@ public class SettingsProvider extends ContentProvider { currentVersion = 226; } + // Version 226: Introduces dreaming while postured setting and migrates user from + // docked dream trigger to postured dream trigger. + if (currentVersion == 226) { + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting dreamOnDock = secureSettings.getSettingLocked( + Secure.SCREENSAVER_ACTIVATE_ON_DOCK); + final Setting dreamsEnabled = secureSettings.getSettingLocked( + Secure.SCREENSAVER_ENABLED); + final boolean dreamOnPosturedDefault = getContext().getResources().getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault); + final boolean dreamsEnabledByDefault = getContext().getResources().getBoolean( + com.android.internal.R.bool.config_dreamsEnabledByDefault); + + if (dreamOnPosturedDefault && !dreamOnDock.isNull() + && dreamOnDock.getValue().equals("1")) { + // Disable dock activation and enable postured. + secureSettings.insertSettingOverrideableByRestoreLocked( + Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + "0", + null, + true, + SettingsState.SYSTEM_PACKAGE_NAME); + secureSettings.insertSettingOverrideableByRestoreLocked( + Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + "1", + null, + true, + SettingsState.SYSTEM_PACKAGE_NAME); + + // Disable dreams overall, so user doesn't start to unexpectedly see dreams + // enabled when postured. + if (!dreamsEnabledByDefault && !dreamsEnabled.isNull() + && dreamsEnabled.getValue().equals("1")) { + secureSettings.insertSettingOverrideableByRestoreLocked( + Secure.SCREENSAVER_ENABLED, + "0", + null, + true, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + + currentVersion = 227; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6491bf5c8f5b..72ae76a45cac 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -1171,5 +1171,16 @@ android:exported="false" /> + <service + android:name="com.google.android.systemui.lowlightclock.LowLightClockDreamService" + android:enabled="false" + android:exported="false" + android:directBootAware="true" + android:permission="android.permission.BIND_DREAM_SERVICE"> + <intent-filter> + <action android:name="android.service.dreams.DreamService" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </service> </application> </manifest> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a92df8026715..f0c855753292 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -541,6 +541,16 @@ flag { } flag { + name: "face_scanning_animation_npe_fix" + namespace: "systemui" + description: "Fix for the face scanning animation NPE" + bug: "392032258" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "indication_text_a11y_fix" namespace: "systemui" description: "add double shadow to the indication text at the bottom of the lock screen" @@ -623,13 +633,6 @@ flag { } flag { - name: "quick_settings_visual_haptics_longpress" - namespace: "systemui" - description: "Enable special visual and haptic effects for quick settings tiles with long-press actions" - bug: "229856884" -} - -flag { name: "switch_user_on_bg" namespace: "systemui" description: "Does user switching on a background thread" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index d7545cb07849..22556777c40f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -26,7 +26,6 @@ import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTran import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition -import com.android.systemui.scene.ui.composable.transitions.notificationsShadeToQuickSettingsShadeTransition import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.toNotificationsShadeTransition import com.android.systemui.scene.ui.composable.transitions.toQuickSettingsShadeTransition @@ -172,13 +171,6 @@ val SceneContainerTransitions = transitions { toQuickSettingsShadeTransition() } from( - Overlays.NotificationsShade, - to = Overlays.QuickSettingsShade, - cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, - ) { - notificationsShadeToQuickSettingsShadeTransition() - } - from( Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index b087ecf1a488..6d75c4ca3a38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -46,7 +46,7 @@ import androidx.test.filters.SmallTest; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.settings.SecureSettings; @@ -86,7 +86,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Mock private IRemoteMagnificationAnimationCallback mAnimationCallback; @Mock - private OverviewProxyService mOverviewProxyService; + private LauncherProxyService mLauncherProxyService; @Mock private SecureSettings mSecureSettings; @Mock @@ -114,7 +114,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { assertNotNull(mTestableLooper); mMagnification = new MagnificationImpl(getContext(), mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue, - mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings, + mModeSwitchesController, mSysUiState, mLauncherProxyService, mSecureSettings, mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager, mAccessibilityManager, mViewCaptureAwareWindowManager); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt index 5e023a203267..6899d23bcfd7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt @@ -34,7 +34,7 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository import com.android.systemui.keyboard.data.repository.keyboardRepository import com.android.systemui.kosmos.testScope -import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener import com.android.systemui.testKosmos import com.android.systemui.touchpad.data.repository.touchpadRepository import com.android.systemui.user.data.repository.fakeUserRepository @@ -68,7 +68,7 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge private val keyboardRepository = kosmos.keyboardRepository private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository private val userRepository = kosmos.fakeUserRepository - private val overviewProxyService = kosmos.mockOverviewProxyService + private val launcherProxyService = kosmos.mockLauncherProxyService private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor private val eduClock = kosmos.fakeEduClock @@ -519,8 +519,8 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge } private fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: GestureType) { - val listenerCaptor = argumentCaptor<OverviewProxyListener>() - verify(overviewProxyService).addCallback(listenerCaptor.capture()) + val listenerCaptor = argumentCaptor<LauncherProxyListener>() + verify(launcherProxyService).addCallback(listenerCaptor.capture()) listenerCaptor.firstValue.updateContextualEduStats(isTrackpadGesture, gestureType) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt index 692b9c67f322..dc393c01dce1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt @@ -32,7 +32,7 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository import com.android.systemui.keyboard.data.repository.keyboardRepository import com.android.systemui.kosmos.testScope -import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener import com.android.systemui.testKosmos import com.android.systemui.touchpad.data.repository.touchpadRepository import com.google.common.truth.Truth.assertThat @@ -58,7 +58,7 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { private val touchpadRepository = kosmos.touchpadRepository private val keyboardRepository = kosmos.keyboardRepository private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository - private val overviewProxyService = kosmos.mockOverviewProxyService + private val launcherProxyService = kosmos.mockLauncherProxyService private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor private val eduClock = kosmos.fakeEduClock @@ -167,16 +167,16 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { keyboardRepository.setIsAnyKeyboardConnected(true) } - private fun getOverviewProxyListener(): OverviewProxyListener { - val listenerCaptor = argumentCaptor<OverviewProxyListener>() - verify(overviewProxyService).addCallback(listenerCaptor.capture()) + private fun getLauncherProxyListener(): LauncherProxyListener { + val listenerCaptor = argumentCaptor<LauncherProxyListener>() + verify(launcherProxyService).addCallback(listenerCaptor.capture()) return listenerCaptor.firstValue } private fun TestScope.triggerEducation(gestureType: GestureType) { // Increment max number of signal to try triggering education for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) { - val listener = getOverviewProxyListener() + val listener = getLauncherProxyListener() listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) } runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt index 698fac107a1d..4d81cb0ce726 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE import android.content.Intent +import android.hardware.input.FakeInputManager import android.hardware.input.InputGestureData import android.hardware.input.InputManager import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS @@ -57,13 +58,14 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() { private val secondaryUserContext: Context = mock() private var activeUserContext: Context = primaryUserContext - private val kosmos = testKosmos().also { - it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { activeUserContext }) - } + private val kosmos = + testKosmos().also { + it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { activeUserContext }) + } private val inputManager = kosmos.fakeInputManager.inputManager private val broadcastDispatcher = kosmos.broadcastDispatcher - private val inputManagerForSecondaryUser: InputManager = mock() + private val inputManagerForSecondaryUser: InputManager = FakeInputManager().inputManager private val testScope = kosmos.testScope private val testHelper = kosmos.shortcutHelperTestHelper private val customInputGesturesRepository = kosmos.customInputGesturesRepository @@ -94,9 +96,10 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() { fun customInputGestures_initialValueReturnsDataFromAPI() { testScope.runTest { val customInputGestures = listOf(allAppsInputGestureData) - whenever( - inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) - ).then { return@then customInputGestures } + whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)) + .then { + return@then customInputGestures + } val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures) @@ -108,9 +111,10 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() { fun customInputGestures_isUpdatedToMostRecentDataAfterNewGestureIsAdded() { testScope.runTest { var customInputGestures = listOf<InputGestureData>() - whenever( - inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) - ).then { return@then customInputGestures } + whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)) + .then { + return@then customInputGestures + } whenever(inputManager.addCustomInputGesture(any())).then { invocation -> val inputGesture = invocation.getArgument<InputGestureData>(0) customInputGestures = customInputGestures + inputGesture @@ -129,10 +133,10 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() { fun retrieveCustomInputGestures_retrievesMostRecentData() { testScope.runTest { var customInputGestures = listOf<InputGestureData>() - whenever( - inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) - ).then { return@then customInputGestures } - + whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)) + .then { + return@then customInputGestures + } assertThat(customInputGesturesRepository.retrieveCustomInputGestures()).isEmpty() @@ -143,24 +147,38 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() { } } + @Test + fun getInputGestureByTrigger_returnsInputGestureFromInputManager() = + testScope.runTest { + inputManager.addCustomInputGesture(allAppsInputGestureData) + + val inputGestureData = + customInputGesturesRepository.getInputGestureByTrigger( + allAppsInputGestureData.trigger + ) + + assertThat(inputGestureData).isEqualTo(allAppsInputGestureData) + } + private fun setCustomInputGesturesForPrimaryUser(vararg inputGesture: InputGestureData) { - whenever( - inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) - ).thenReturn(inputGesture.toList()) + whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)) + .thenReturn(inputGesture.toList()) } private fun setCustomInputGesturesForSecondaryUser(vararg inputGesture: InputGestureData) { whenever( - inputManagerForSecondaryUser.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) - ).thenReturn(inputGesture.toList()) + inputManagerForSecondaryUser.getCustomInputGestures( + /* filter= */ InputGestureData.Filter.KEY + ) + ) + .thenReturn(inputGesture.toList()) } private fun switchToSecondaryUser() { activeUserContext = secondaryUserContext broadcastDispatcher.sendIntentToMatchingReceiversOnly( context, - Intent(Intent.ACTION_USER_SWITCHED) + Intent(Intent.ACTION_USER_SWITCHED), ) } - -}
\ No newline at end of file +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt index 4cfb26e6555b..522572dcffb7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt @@ -24,6 +24,7 @@ import android.hardware.input.InputGestureData import android.hardware.input.InputGestureData.createKeyTrigger import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION import android.hardware.input.fakeInputManager import android.platform.test.annotations.DisableFlags @@ -336,28 +337,6 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { } } - private suspend fun customizeShortcut( - customizationRequest: ShortcutCustomizationRequestInfo, - keyCombination: KeyCombination? = null, - ): ShortcutCustomizationRequestResult { - repo.onCustomizationRequested(customizationRequest) - repo.updateUserKeyCombination(keyCombination) - - return when (customizationRequest) { - is SingleShortcutCustomization.Add -> { - repo.confirmAndSetShortcutCurrentlyBeingCustomized() - } - - is SingleShortcutCustomization.Delete -> { - repo.deleteShortcutCurrentlyBeingCustomized() - } - - else -> { - ShortcutCustomizationRequestResult.ERROR_OTHER - } - } - } - @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_isUpdatedAfterCustomShortcutsAreReset() { @@ -387,10 +366,66 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { } } + @Test + fun selectedKeyCombinationIsAvailable_whenTriggerIsNotRegisteredInInputManager() = + testScope.runTest { + helper.toggle(deviceId = 123) + repo.onCustomizationRequested(allAppsShortcutAddRequest) + repo.updateUserKeyCombination(standardKeyCombination) + + assertThat(repo.isSelectedKeyCombinationAvailable()).isTrue() + } + + @Test + fun selectedKeyCombinationIsNotAvailable_whenTriggerIsRegisteredInInputManager() = + testScope.runTest { + inputManager.addCustomInputGesture(buildInputGestureWithStandardKeyCombination()) + + helper.toggle(deviceId = 123) + repo.onCustomizationRequested(allAppsShortcutAddRequest) + repo.updateUserKeyCombination(standardKeyCombination) + + assertThat(repo.isSelectedKeyCombinationAvailable()).isFalse() + } + private fun setApiAppLaunchBookmarks(appLaunchBookmarks: List<InputGestureData>) { whenever(inputManager.appLaunchBookmarks).thenReturn(appLaunchBookmarks) } + private suspend fun customizeShortcut( + customizationRequest: ShortcutCustomizationRequestInfo, + keyCombination: KeyCombination? = null, + ): ShortcutCustomizationRequestResult { + repo.onCustomizationRequested(customizationRequest) + repo.updateUserKeyCombination(keyCombination) + + return when (customizationRequest) { + is SingleShortcutCustomization.Add -> { + repo.confirmAndSetShortcutCurrentlyBeingCustomized() + } + + is SingleShortcutCustomization.Delete -> { + repo.deleteShortcutCurrentlyBeingCustomized() + } + + else -> { + ShortcutCustomizationRequestResult.ERROR_OTHER + } + } + } + + private fun buildInputGestureWithStandardKeyCombination() = + InputGestureData.Builder() + .setKeyGestureType(KEY_GESTURE_TYPE_HOME) + .setTrigger( + createKeyTrigger( + /* keycode= */ standardKeyCombination.keyCode!!, + /* modifierState= */ standardKeyCombination.modifiers and + ALL_SUPPORTED_MODIFIERS, + ) + ) + .build() + private fun simpleInputGestureDataForAppLaunchShortcut( keyCode: Int = KEYCODE_A, modifiers: Int = META_CTRL_ON or META_ALT_ON, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt index d9d34f5ace7b..6eef5eb09812 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt @@ -18,11 +18,15 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel import android.content.Context import android.content.Context.INPUT_SERVICE -import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS +import android.hardware.input.InputGestureData +import android.hardware.input.InputGestureData.createKeyTrigger import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE -import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME import android.hardware.input.fakeInputManager +import android.view.KeyEvent.KEYCODE_A +import android.view.KeyEvent.META_CTRL_ON +import android.view.KeyEvent.META_META_ON import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -30,7 +34,6 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest -import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed @@ -44,16 +47,17 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiSt import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.userTracker import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -63,7 +67,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { private val mockUserContext: Context = mock() private val kosmos = - testKosmos().also { + testKosmos().useUnconfinedTestDispatcher().also { it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } private val testScope = kosmos.testScope @@ -75,6 +79,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun setup() { helper.showFromActivity() whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) + testScope.backgroundScope.launch { viewModel.activate() } } @Test @@ -146,8 +151,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun uiState_becomeInactiveAfterSuccessfullySettingShortcut() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - whenever(inputManager.addCustomInputGesture(any())) - .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) openAddShortcutDialogAndSetShortcut() @@ -166,11 +169,38 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } @Test - fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() { + fun uiState_errorMessage_onKeyPressed_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() { testScope.runTest { + inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger()) + val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) + + openAddShortcutDialogAndPressKeyCombination() + + assertThat((uiState as AddShortcutDialog).errorMessage) + .isEqualTo( + context.getString( + R.string.shortcut_customizer_key_combination_in_use_error_message + ) + ) + } + } + + @Test + fun uiState_errorMessage_onKeyPressed_isEmpty_whenKeyCombinationIsAvailable() { + testScope.runTest { + val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) + + openAddShortcutDialogAndPressKeyCombination() + + assertThat((uiState as AddShortcutDialog).errorMessage).isEmpty() + } + } + + @Test + fun uiState_errorMessage_onSetShortcut_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() { + testScope.runTest { + inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger()) val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - whenever(inputManager.addCustomInputGesture(any())) - .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS) openAddShortcutDialogAndSetShortcut() @@ -184,11 +214,12 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } @Test - fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationIsReserved() { + fun uiState_errorMessage_onSetShortcut_isKeyCombinationInUse_whenKeyCombinationIsReserved() { testScope.runTest { + inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger()) + kosmos.fakeInputManager.addCustomInputGestureErrorCode = + CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - whenever(inputManager.addCustomInputGesture(any())) - .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE) openAddShortcutDialogAndSetShortcut() @@ -202,11 +233,12 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } @Test - fun uiState_errorMessage_isGenericError_whenErrorIsUnknown() { + fun uiState_errorMessage_onSetShortcut_isGenericError_whenErrorIsUnknown() { testScope.runTest { + inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger()) + kosmos.fakeInputManager.addCustomInputGestureErrorCode = + CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - whenever(inputManager.addCustomInputGesture(any())) - .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER) openAddShortcutDialogAndSetShortcut() @@ -219,10 +251,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun uiState_becomesInactiveAfterSuccessfullyDeletingShortcut() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - whenever(inputManager.getCustomInputGestures(any())) - .thenReturn(listOf(goHomeInputGestureData, allAppsInputGestureData)) - whenever(inputManager.removeCustomInputGesture(any())) - .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) + inputManager.addCustomInputGesture(allAppsInputGestureData) openDeleteShortcutDialogAndDeleteShortcut() @@ -234,7 +263,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun uiState_becomesInactiveAfterSuccessfullyResettingShortcuts() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - whenever(inputManager.getCustomInputGestures(any())).thenReturn(emptyList()) openResetShortcutDialogAndResetAllCustomShortcuts() @@ -297,24 +325,42 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } } + @Test + fun uiState_pressedKeys_resetsToEmpty_onClearSelectedShortcutKeyCombination() { + testScope.runTest { + val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) + viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest) + viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed) + viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed) + viewModel.clearSelectedKeyCombination() + assertThat((uiState as AddShortcutDialog).pressedKeys).isEmpty() + } + } + private suspend fun openAddShortcutDialogAndSetShortcut() { - viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) + openAddShortcutDialogAndPressKeyCombination() + viewModel.onSetShortcut() + } + private fun openAddShortcutDialogAndPressKeyCombination() { + viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed) viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed) - - viewModel.onSetShortcut() } private suspend fun openDeleteShortcutDialogAndDeleteShortcut() { viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) - viewModel.deleteShortcutCurrentlyBeingCustomized() } private suspend fun openResetShortcutDialogAndResetAllCustomShortcuts() { viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset) - viewModel.resetAllCustomShortcuts() } + + private fun buildSimpleInputGestureWithMetaCtrlATrigger() = + InputGestureData.Builder() + .setKeyGestureType(KEY_GESTURE_TYPE_HOME) + .setTrigger(createKeyTrigger(KEYCODE_A, META_CTRL_ON or META_META_ON)) + .build() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index c8a16483a00c..abcbdb153e80 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -27,6 +27,8 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 +import com.android.systemui.Flags.glanceableHubV2 import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor @@ -34,6 +36,8 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable +import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled +import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.DisableSceneContainer @@ -153,6 +157,9 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest if (!SceneContainerFlag.isEnabled) { mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) } + if (glanceableHubV2()) { + kosmos.setCommunalV2ConfigEnabled(true) + } featureFlags = FakeFeatureFlags() fromLockscreenTransitionInteractor.start() @@ -1948,6 +1955,39 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_GLANCEABLE_HUB_V2) + fun glanceableHubToDreaming_v2() = + testScope.runTest { + kosmos.setCommunalV2Enabled(true) + + // GIVEN a device that is not dreaming or dozing + keyguardRepository.setDreamingWithOverlay(false) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + advanceTimeBy(600.milliseconds) + + // GIVEN a prior transition has run to glanceable hub + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + clearInvocations(transitionRepository) + + keyguardRepository.setDreamingWithOverlay(true) + advanceTimeBy(100.milliseconds) + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToDreaming() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index 9d5bf4dbdc3f..a276f514b779 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -1045,6 +1045,41 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { assertThat(usingKeyguardGoingAwayAnimation).isFalse() } + @Test + fun aodVisibility_visibleFullyInAod_falseOtherwise() = + testScope.runTest { + val aodVisibility by collectValues(underTest.value.aodVisibility) + + transitionRepository.sendTransitionStepsThroughRunning( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope, + throughValue = 0.5f, + ) + + assertEquals(listOf(false), aodVisibility) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.FINISHED, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + ) + ) + runCurrent() + + assertEquals(listOf(false, true), aodVisibility) + + transitionRepository.sendTransitionStepsThroughRunning( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + testScope, + ) + runCurrent() + + assertEquals(listOf(false, true, false), aodVisibility) + } + companion object { private val progress = MutableStateFlow(0f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt index 286f8bfd63a2..e93ed39274fb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt @@ -20,6 +20,7 @@ import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP +import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.BrokenWithSceneContainer @@ -97,6 +98,7 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() } @Test + @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun blurRadiusGoesToMaximumWhenShadeIsExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index 60a19a4c7d07..aaca603ecf77 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -21,6 +21,7 @@ import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP +import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -155,6 +156,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza } @Test + @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) @BrokenWithSceneContainer(388068805) fun blurRadiusIsMaxWhenShadeIsExpanded() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt index 6db876756d3a..0951df24c56f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt @@ -16,50 +16,96 @@ package com.android.systemui.keyguard.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues -import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidJUnit4::class) -class OccludedToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class OccludedToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) : + SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val underTest by lazy { kosmos.occludedToPrimaryBouncerTransitionViewModel } + private lateinit var underTest: OccludedToPrimaryBouncerTransitionViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + underTest = kosmos.occludedToPrimaryBouncerTransitionViewModel + } @Test - @DisableSceneContainer - fun blurBecomesMaxValueImmediately() = + @BrokenWithSceneContainer(388068805) + fun notificationsAreBlurredImmediatelyWhenBouncerIsOpenedAndShadeIsExpanded() = + testScope.runTest { + val values by collectValues(underTest.notificationBlurRadius) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) + + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = kosmos.blurConfig.maxBlurRadiusPx, + endValue = kosmos.blurConfig.maxBlurRadiusPx, + actualValuesProvider = { values }, + transitionFactory = ::step, + checkInterpolatedValues = false, + ) + } + + @Test + @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) + @BrokenWithSceneContainer(388068805) + fun blurBecomesMaxValueImmediatelyWhenShadeIsAlreadyExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, actualValuesProvider = { values }, - transitionFactory = { step, transitionState -> - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.PRIMARY_BOUNCER, - value = step, - transitionState = transitionState, - ownerName = "OccludedToPrimaryBouncerTransitionViewModelTest", - ) - }, + transitionFactory = ::step, checkInterpolatedValues = false, ) } + + fun step(value: Float, state: TransitionState = TransitionState.RUNNING): TransitionStep { + return TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.PRIMARY_BOUNCER, + value = value, + transitionState = state, + ownerName = "OccludedToPrimaryBouncerTransitionViewModelTest", + ) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt index 0db0c5fe8482..8fefb8d40b71 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.keyguard.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -35,13 +39,17 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidJUnit4::class) -class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class PrimaryBouncerToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : + SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope @@ -49,9 +57,27 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository val biometricSettingsRepository = kosmos.biometricSettingsRepository - val underTest = kosmos.primaryBouncerToLockscreenTransitionViewModel + private lateinit var underTest: PrimaryBouncerToLockscreenTransitionViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + underTest = kosmos.primaryBouncerToLockscreenTransitionViewModel + } @Test + @BrokenWithSceneContainer(392346450) fun lockscreenAlphaStartsFromViewStateAccessorAlpha() = testScope.runTest { val viewState = ViewStateAccessor(alpha = { 0.5f }) @@ -70,6 +96,7 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(392346450) fun deviceEntryParentViewAlpha() = testScope.runTest { val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) @@ -89,6 +116,7 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(392346450) fun deviceEntryBackgroundViewAlpha_udfpsEnrolled_show() = testScope.runTest { fingerprintPropertyRepository.supportsUdfps() @@ -113,6 +141,7 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(388068805) fun blurRadiusGoesFromMaxToMinWhenShadeIsNotExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) @@ -128,6 +157,8 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) + @BrokenWithSceneContainer(388068805) fun blurRadiusRemainsAtMaxWhenShadeIsExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt index b0b4af5fea5b..fd7fb9f863c8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt @@ -16,11 +16,13 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues -import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig @@ -40,10 +42,37 @@ class PrimaryBouncerToOccludedTransitionViewModelTest : SysuiTestCase() { private val underTest by lazy { kosmos.primaryBouncerToOccludedTransitionViewModel } @Test - @DisableSceneContainer - fun blurBecomesMaxValueImmediately() = + @BrokenWithSceneContainer(388068805) + fun blurBecomesMinValueImmediatelyWhenShadeIsNotExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(false) + + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = kosmos.blurConfig.minBlurRadiusPx, + endValue = kosmos.blurConfig.minBlurRadiusPx, + actualValuesProvider = { values }, + transitionFactory = { step, transitionState -> + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.OCCLUDED, + value = step, + transitionState = transitionState, + ownerName = "PrimaryBouncerToOccludedTransitionViewModelTest", + ) + }, + checkInterpolatedValues = false, + ) + } + + @Test + @BrokenWithSceneContainer(388068805) + @EnableFlags(Flags.FLAG_NOTIFICATION_SHADE_BLUR) + fun blurBecomesMaxValueImmediatelyWhenShadeIsExpanded() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(false) kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 63ec78fd9ee5..6db5fb4f7d7c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -265,7 +265,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mSeekBar.getContentDescription()).isNotNull(); - assertThat(mViewHolder.mSeekBar.getAccessibilityDelegate()).isNotNull(); assertThat(mViewHolder.mContainerLayout.isFocusable()).isFalse(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/OWNERS new file mode 100644 index 000000000000..739d2ac2e87b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/OWNERS @@ -0,0 +1 @@ +file:/packages/SystemUI/src/com/android/systemui/media/dialog/OWNERS diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java index a770ee199ba6..c1872f05aa1d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -57,7 +57,7 @@ import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; @@ -104,7 +104,7 @@ public class NavBarHelperTest extends SysuiTestCase { @Mock SystemActions mSystemActions; @Mock - OverviewProxyService mOverviewProxyService; + LauncherProxyService mLauncherProxyService; @Mock Lazy<AssistManager> mAssistManagerLazy; @Mock @@ -161,7 +161,7 @@ public class NavBarHelperTest extends SysuiTestCase { mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager, mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver, mAccessibilityGestureTargetObserver, - mSystemActions, mOverviewProxyService, mAssistManagerLazy, + mSystemActions, mLauncherProxyService, mAssistManagerLazy, () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class), mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker, mDisplayTracker, mNotificationShadeWindowController, mConfigurationController, @@ -171,7 +171,7 @@ public class NavBarHelperTest extends SysuiTestCase { @Test public void registerListenersInCtor() { verify(mNavigationModeController, times(1)).addListener(mNavBarHelper); - verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper); + verify(mLauncherProxyService, times(1)).addCallback(mNavBarHelper); verify(mCommandQueue, times(1)).addCallback(any()); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt index 9bae7bd72f7d..cf0a25020d93 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt @@ -9,7 +9,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.model.SysUiState import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.recents.LauncherProxyService import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.shared.system.TaskStackChangeListeners @@ -49,7 +49,7 @@ class TaskbarDelegateTest : SysuiTestCase() { @Mock lateinit var mLightBarControllerFactory: LightBarTransitionsController.Factory @Mock lateinit var mLightBarTransitionController: LightBarTransitionsController @Mock lateinit var mCommandQueue: CommandQueue - @Mock lateinit var mOverviewProxyService: OverviewProxyService + @Mock lateinit var mLauncherProxyService: LauncherProxyService @Mock lateinit var mNavBarHelper: NavBarHelper @Mock lateinit var mNavigationModeController: NavigationModeController @Mock lateinit var mSysUiState: SysUiState @@ -87,7 +87,7 @@ class TaskbarDelegateTest : SysuiTestCase() { ) mTaskbarDelegate.setDependencies( mCommandQueue, - mOverviewProxyService, + mLauncherProxyService, mNavBarHelper, mNavigationModeController, mSysUiState, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java index 00e79f5a3ac2..fd4bb4bb8a37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java @@ -40,7 +40,7 @@ import com.android.systemui.SysuiTestableContext; import com.android.systemui.assist.AssistManager; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -80,7 +80,7 @@ public class NavigationBarButtonTest extends SysuiTestCase { .thenReturn(mEdgeBackGestureHandler); mDependency.injectMockDependency(AssistManager.class); - mDependency.injectMockDependency(OverviewProxyService.class); + mDependency.injectMockDependency(LauncherProxyService.class); mDependency.injectMockDependency(KeyguardStateController.class); mDependency.injectMockDependency(NavigationBarController.class); mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java index e58c8f281fc1..85c093c16d88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java @@ -35,7 +35,7 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import org.junit.After; import org.junit.Before; @@ -55,7 +55,7 @@ public class NavigationBarInflaterViewTest extends SysuiTestCase { @Before public void setUp() { mDependency.injectMockDependency(AssistManager.class); - mDependency.injectMockDependency(OverviewProxyService.class); + mDependency.injectMockDependency(LauncherProxyService.class); mDependency.injectMockDependency(NavigationModeController.class); mDependency.injectMockDependency(NavigationBarController.class); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java index b7845b73b416..09e49eb217b0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java @@ -106,7 +106,7 @@ import com.android.systemui.navigationbar.views.buttons.KeyButtonView; import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger; import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.FakeDisplayTracker; @@ -185,7 +185,7 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private SystemActions mSystemActions; @Mock - private OverviewProxyService mOverviewProxyService; + private LauncherProxyService mLauncherProxyService; @Mock private StatusBarStateController mStatusBarStateController; @Mock @@ -285,14 +285,14 @@ public class NavigationBarTest extends SysuiTestCase { mDependency.injectMockDependency(KeyguardStateController.class); mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController); mDependency.injectMockDependency(NavigationBarController.class); - mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService); + mDependency.injectTestDependency(LauncherProxyService.class, mLauncherProxyService); mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController); TestableLooper.get(this).runWithLooper(() -> { mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class), mock(AccessibilityButtonModeObserver.class), mock(AccessibilityButtonTargetsObserver.class), mock(AccessibilityGestureTargetsObserver.class), - mSystemActions, mOverviewProxyService, + mSystemActions, mLauncherProxyService, () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces), mKeyguardStateController, mock(NavigationModeController.class), mEdgeBackGestureHandlerFactory, mock(IWindowManager.class), @@ -690,7 +690,7 @@ public class NavigationBarTest extends SysuiTestCase { mock(AccessibilityManager.class), deviceProvisionedController, new MetricsLogger(), - mOverviewProxyService, + mLauncherProxyService, mNavigationModeController, mStatusBarStateController, mStatusBarKeyguardViewManager, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java index 3621ab975daf..cff9beccc729 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java @@ -36,7 +36,7 @@ import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarTransitionsController; @@ -72,7 +72,7 @@ public class NavigationBarTransitionsTest extends SysuiTestCase { when(mEdgeBackGestureHandlerFactory.create(any(Context.class))) .thenReturn(mEdgeBackGestureHandler); mDependency.injectMockDependency(AssistManager.class); - mDependency.injectMockDependency(OverviewProxyService.class); + mDependency.injectMockDependency(LauncherProxyService.class); mDependency.injectMockDependency(StatusBarStateController.class); mDependency.injectMockDependency(KeyguardStateController.class); mDependency.injectMockDependency(NavigationBarController.class); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java index 403a883e1760..58ec0c7a0e72 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java @@ -51,7 +51,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import org.junit.Before; import org.junit.Test; @@ -76,7 +76,7 @@ public class KeyButtonViewTest extends SysuiTestCase { public void setup() throws Exception { MockitoAnnotations.initMocks(this); mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); - mDependency.injectMockDependency(OverviewProxyService.class); + mDependency.injectMockDependency(LauncherProxyService.class); mDependency.injectMockDependency(AssistManager.class); mUiEventLogger = mDependency.injectMockDependency(UiEventLogger.class); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt index 855931c32671..52b9e47e6d3d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt @@ -21,7 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.UserActionResult +import com.android.compose.animation.scene.UserActionResult.HideOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer @@ -53,7 +55,7 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() { val actions by collectLastValue(underTest.actions) underTest.activateIn(this) - assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay) + assertThat((actions?.get(Swipe.Up) as? HideOverlay)?.overlay) .isEqualTo(Overlays.NotificationsShade) assertThat(actions?.get(Swipe.Down)).isNull() } @@ -64,7 +66,7 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() { val actions by collectLastValue(underTest.actions) underTest.activateIn(this) - assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay) + assertThat((actions?.get(Back) as? HideOverlay)?.overlay) .isEqualTo(Overlays.NotificationsShade) } @@ -74,11 +76,11 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() { val actions by collectLastValue(underTest.actions) underTest.activateIn(this) - assertThat( - (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopRight)) - as? UserActionResult.ReplaceByOverlay) - ?.overlay - ) - .isEqualTo(Overlays.QuickSettingsShade) + val action = + (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopRight)) as? ShowOverlay) + assertThat(action?.overlay).isEqualTo(Overlays.QuickSettingsShade) + val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some + assertThat(overlaysToHide).isNotNull() + assertThat(overlaysToHide?.overlays).containsExactly(Overlays.NotificationsShade) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index e9633f49f76d..ff005c2b767a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -16,7 +16,6 @@ package com.android.systemui.qs; -import static com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS; import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag; import static com.google.common.truth.Truth.assertThat; @@ -41,8 +40,6 @@ import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; import android.content.res.Configuration; import android.content.res.Resources; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper.RunWithLooper; import android.view.ContextThemeWrapper; @@ -87,6 +84,7 @@ import javax.inject.Provider; import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.StateFlow; + import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; @@ -505,7 +503,6 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { } @Test - @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS) public void setTiles_longPressEffectEnabled_nonNullLongPressEffectsAreProvided() { mLongPressEffectProvider.mEffectsProvided = 0; when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile)); @@ -516,16 +513,6 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS) - public void setTiles_longPressEffectDisabled_noLongPressEffectsAreProvided() { - mLongPressEffectProvider.mEffectsProvided = 0; - when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile)); - mController.setTiles(); - - assertThat(mLongPressEffectProvider.mEffectsProvided).isEqualTo(0); - } - - @Test public void setTiles_differentTiles_extraTileRemoved() { when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile)); mController.setTiles(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index fecd8c3cacca..4c834b396df6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.qs.tiles.dialog.WifiStateWorker import com.android.systemui.res.R @@ -109,6 +110,7 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var dialogManager: InternetDialogManager @Mock private lateinit var wifiStateWorker: WifiStateWorker @Mock private lateinit var accessPointController: AccessPointController + @Mock private lateinit var internetDetailsViewModelFactory: InternetDetailsViewModel.Factory @Before fun setUp() { @@ -145,6 +147,7 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() { dialogManager, wifiStateWorker, accessPointController, + internetDetailsViewModelFactory ) underTest.initialize() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt index e4a988860a6e..ce4a3432a5b4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt @@ -26,13 +26,13 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.dialog.InternetDetailsContentManager +import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.qs.tiles.dialog.WifiStateWorker import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel import com.android.systemui.statusbar.connectivity.AccessPointController -import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable -import com.google.common.truth.Truth import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before @@ -40,9 +40,10 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.eq -import org.mockito.Mock -import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.mock import org.mockito.kotlin.times +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @SmallTest @@ -54,15 +55,27 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() { private lateinit var underTest: InternetTileUserActionInteractor - @Mock private lateinit var internetDialogManager: InternetDialogManager - @Mock private lateinit var wifiStateWorker: WifiStateWorker - @Mock private lateinit var controller: AccessPointController + private lateinit var internetDialogManager: InternetDialogManager + private lateinit var wifiStateWorker: WifiStateWorker + private lateinit var controller: AccessPointController + private lateinit var internetDetailsViewModelFactory: InternetDetailsViewModel.Factory + private lateinit var internetDetailsContentManagerFactory: InternetDetailsContentManager.Factory + private lateinit var internetDetailsViewModel: InternetDetailsViewModel @Before fun setup() { internetDialogManager = mock<InternetDialogManager>() wifiStateWorker = mock<WifiStateWorker>() controller = mock<AccessPointController>() + internetDetailsViewModelFactory = mock<InternetDetailsViewModel.Factory>() + internetDetailsContentManagerFactory = mock<InternetDetailsContentManager.Factory>() + internetDetailsViewModel = + InternetDetailsViewModel( + onLongClick = {}, + accessPointController = mock<AccessPointController>(), + contentManagerFactory = internetDetailsContentManagerFactory, + ) + whenever(internetDetailsViewModelFactory.create(any())).thenReturn(internetDetailsViewModel) underTest = InternetTileUserActionInteractor( @@ -71,6 +84,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() { wifiStateWorker, controller, inputHandler, + internetDetailsViewModelFactory, ) } @@ -102,7 +116,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() { underTest.handleInput(QSTileInputTestKtx.longClick(input)) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { - Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS) + assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS) } } @@ -114,7 +128,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() { underTest.handleInput(QSTileInputTestKtx.longClick(input)) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { - Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS) + assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS) } } @@ -141,8 +155,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() { @Test fun detailsViewModel() = kosmos.testScope.runTest { - assertThat(underTest.detailsViewModel.getTitle()) - .isEqualTo("Internet") + assertThat(underTest.detailsViewModel.getTitle()).isEqualTo("Internet") assertThat(underTest.detailsViewModel.getSubTitle()) .isEqualTo("Tab a network to connect") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt index 939644594d31..df2dd99c779e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt @@ -21,7 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.UserActionResult +import com.android.compose.animation.scene.UserActionResult.HideOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer @@ -53,7 +55,7 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() { val actions by collectLastValue(underTest.actions) underTest.activateIn(this) - assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay) + assertThat((actions?.get(Swipe.Up) as? HideOverlay)?.overlay) .isEqualTo(Overlays.QuickSettingsShade) assertThat(actions?.get(Swipe.Down)).isNull() } @@ -66,7 +68,7 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() { underTest.activateIn(this) assertThat(isEditing).isFalse() - assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay) + assertThat((actions?.get(Back) as? HideOverlay)?.overlay) .isEqualTo(Overlays.QuickSettingsShade) } @@ -87,11 +89,11 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() { val actions by collectLastValue(underTest.actions) underTest.activateIn(this) - assertThat( - (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopLeft)) - as? UserActionResult.ReplaceByOverlay) - ?.overlay - ) - .isEqualTo(Overlays.NotificationsShade) + val action = + (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopLeft)) as? ShowOverlay) + assertThat(action?.overlay).isEqualTo(Overlays.NotificationsShade) + val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some + assertThat(overlaysToHide).isNotNull() + assertThat(overlaysToHide?.overlays).containsExactly(Overlays.QuickSettingsShade) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 0713a247a4a3..baaf6c9a76ae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -84,7 +84,7 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.log.LogWtfHandlerRule; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -156,7 +156,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Mock private NotificationClickNotifier mClickNotifier; @Mock - private OverviewProxyService mOverviewProxyService; + private LauncherProxyService mLauncherProxyService; @Mock private KeyguardManager mKeyguardManager; @Mock @@ -1142,7 +1142,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { (() -> mVisibilityProvider), (() -> mNotifCollection), mClickNotifier, - (() -> mOverviewProxyService), + (() -> mLauncherProxyService), NotificationLockscreenUserManagerTest.this.mKeyguardManager, mStatusBarStateController, mMainExecutor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 55f3717535b7..942e6554e5d9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifCh import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository @@ -44,6 +45,7 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -629,22 +631,26 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) - fun chips_clickingChipNotifiesInteractor() = + @DisableFlags( + FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME, + ) + fun chips_chipsModernizationDisabled_clickingChipNotifiesInteractor() = kosmos.runTest { val latest by collectLastValue(underTest.chips) - val latestChipTap by + val latestChipTapKey by collectLastValue( kosmos.statusBarNotificationChipsInteractor.promotedNotificationChipTapEvent ) + val key = "clickTest" setNotifs( listOf( activeNotificationModel( - key = "clickTest", + key, statusBarChipIcon = createStatusBarIconViewOrNull(), - promotedContent = - PromotedNotificationContentModel.Builder("clickTest").build(), + promotedContent = PromotedNotificationContentModel.Builder(key).build(), ) ) ) @@ -652,7 +658,41 @@ class NotifChipsViewModelTest : SysuiTestCase() { chip.onClickListenerLegacy!!.onClick(mock<View>()) - assertThat(latestChipTap).isEqualTo("clickTest") + assertThat(latestChipTapKey).isEqualTo(key) + } + + @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chips_chipsModernizationEnabled_clickingChipNotifiesInteractor() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + val latestChipTapKey by + collectLastValue( + kosmos.statusBarNotificationChipsInteractor.promotedNotificationChipTapEvent + ) + val key = "clickTest" + + setNotifs( + listOf( + activeNotificationModel( + key, + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = PromotedNotificationContentModel.Builder(key).build(), + ) + ) + ) + val chip = latest!![0] + + assertThat(chip.clickBehavior) + .isInstanceOf( + OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java + ) + + (chip.clickBehavior as OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification) + .onClick() + + assertThat(latestChipTapKey).isEqualTo(key) } private fun setNotifs(notifs: List<ActiveNotificationModel>) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt index 3c772fdbe0b2..356eedbc9a45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC +import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection @@ -242,4 +243,42 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() { // Then: need no re-inflation assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) } + + @Test + @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION_UI) + fun changeIsSummarization_needReInflation_newlySummarized() { + // Given: an Entry with no summarization + val oldAdjustment = adjustmentProvider.calculateAdjustment(entry) + assertThat(oldAdjustment.summarization).isNull() + + // When: the Entry now has a summarization + val rb = RankingBuilder(entry.ranking) + rb.setSummarization("summary!") + entry.ranking = rb.build() + val newAdjustment = adjustmentProvider.calculateAdjustment(entry) + assertThat(newAdjustment).isNotEqualTo(oldAdjustment) + + // Then: Need re-inflation + assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) + } + + @Test + @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION_UI) + fun changeIsSummarization_needReInflation_summarizationChanged() { + // Given: an Entry with no summarization + val rb = RankingBuilder(entry.ranking) + rb.setSummarization("summary!") + entry.ranking = rb.build() + val oldAdjustment = adjustmentProvider.calculateAdjustment(entry) + + // When: the Entry now has a new summarization + val rb2 = RankingBuilder(entry.ranking) + rb2.setSummarization("summary new!") + entry.ranking = rb2.build() + val newAdjustment = adjustmentProvider.calculateAdjustment(entry) + assertThat(newAdjustment).isNotEqualTo(oldAdjustment) + + // Then: Need re-inflation + assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) + } } diff --git a/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml index f7dac13888c0..ea494b4642d5 100644 --- a/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml +++ b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml @@ -21,7 +21,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/keyguard_lock_padding" - android:focusable="true" + android:focusable="false" /> <com.android.keyguard.BouncerKeyguardMessageArea @@ -30,6 +30,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/secondary_message_padding" - android:focusable="true" /> + android:focusable="false" /> </merge> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml index 2a8f1b596711..f231df2f1a10 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml @@ -66,7 +66,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml index 76f6f599c54c..04457229d573 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -31,7 +31,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml index 5879c110d8a1..b184344f2f24 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml @@ -67,7 +67,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml index 3f7b02835357..0e15ff66f3ee 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml @@ -35,7 +35,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml index b464fb3bafed..f6ac02aee657 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml @@ -74,7 +74,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml index 21580731aed2..ba4da794d777 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml @@ -32,7 +32,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml index cc99f5e125f3..dd5f7e4e2ed4 100644 --- a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml +++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml @@ -30,7 +30,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:textAppearance="@style/TextAppearance.QS.Status.Carriers" + android:textAppearance="@style/TextAppearance.QS.Status" android:layout_marginEnd="@dimen/qs_carrier_margin_width" android:visibility="gone" android:textDirection="locale" diff --git a/packages/SystemUI/res/layout/low_light_clock_dream.xml b/packages/SystemUI/res/layout/low_light_clock_dream.xml new file mode 100644 index 000000000000..3d74a9fd8ae3 --- /dev/null +++ b/packages/SystemUI/res/layout/low_light_clock_dream.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/low_light_clock_dream" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/low_light_clock_background_color"> + + <TextClock + android:id="@+id/low_light_text_clock" + android:layout_width="match_parent" + android:layout_height="@dimen/low_light_clock_text_size" + android:layout_gravity="center" + android:fontFamily="google-sans-clock" + android:gravity="center_horizontal" + android:textColor="@color/low_light_clock_text_color" + android:autoSizeTextType="uniform" + android:autoSizeMaxTextSize="@dimen/low_light_clock_text_size" + android:format12Hour="h:mm" + android:format24Hour="H:mm"/> + + <TextView + android:id="@+id/charging_status_text_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom" + android:gravity="center" + android:minHeight="@dimen/low_light_clock_charging_text_min_height" + android:layout_gravity="center_horizontal|bottom" + android:paddingStart="@dimen/keyguard_indication_text_padding" + android:paddingEnd="@dimen/keyguard_indication_text_padding" + android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" + android:textSize="@dimen/low_light_clock_charging_text_size" + android:textFontWeight="@integer/low_light_clock_charging_text_font_weight" + android:maxLines="2" + android:ellipsize="end" + android:accessibilityLiveRegion="polite" /> + </FrameLayout> + diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml index 21e0d2c0b8d7..f943f8660d65 100644 --- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml +++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml @@ -52,7 +52,6 @@ android:layout_height="64dp" android:focusable="false" android:importantForAccessibility="no" - android:background="@drawable/media_output_title_icon_area" android:layout_gravity="center_vertical|start"> <ImageView android:id="@+id/title_icon" diff --git a/packages/SystemUI/res/layout/shade_carrier.xml b/packages/SystemUI/res/layout/shade_carrier.xml index 0fed393a7ed3..6a5df9c3ed10 100644 --- a/packages/SystemUI/res/layout/shade_carrier.xml +++ b/packages/SystemUI/res/layout/shade_carrier.xml @@ -33,7 +33,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:textAppearance="@style/TextAppearance.QS.Status.Carriers" + android:textAppearance="@style/TextAppearance.QS.Status" android:textDirection="locale" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" diff --git a/packages/SystemUI/res/layout/shade_carrier_group.xml b/packages/SystemUI/res/layout/shade_carrier_group.xml index 2e8f98cbd190..6551f3b8160d 100644 --- a/packages/SystemUI/res/layout/shade_carrier_group.xml +++ b/packages/SystemUI/res/layout/shade_carrier_group.xml @@ -32,7 +32,7 @@ android:minWidth="48dp" android:minHeight="48dp" android:gravity="center_vertical" - android:textAppearance="@style/TextAppearance.QS.Status.Carriers.NoCarrierText" + android:textAppearance="@style/TextAppearance.QS.Status" android:textDirection="locale" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 36ede64f91d9..015e0e83e57d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -24,6 +24,7 @@ <color name="qs_tile_divider">#29ffffff</color><!-- 16% white --> <color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white --> <color name="status_bar_clock_color">#FFFFFFFF</color> + <color name="shade_header_text_color">#FFFFFFFF</color> <color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black --> <color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white --> <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black --> @@ -260,4 +261,8 @@ <!-- Rear Display Education --> <color name="rear_display_overlay_animation_background_color">#1E1B17</color> <color name="rear_display_overlay_dialog_background_color">#1E1B17</color> + + <!-- Low light Dream --> + <color name="low_light_clock_background_color">#000000</color> + <color name="low_light_clock_text_color">#CCCCCC</color> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 724a12c12490..c7f037f3d619 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1614,6 +1614,18 @@ <!-- GLANCEABLE_HUB -> DREAMING transition: Amount to shift dream overlay on entering --> <dimen name="hub_to_dreaming_transition_dream_overlay_translation_x">824dp</dimen> + <!-- Low light clock --> + <!-- The text size of the low light clock is intentionally defined in dp to avoid scaling --> + <dimen name="low_light_clock_text_size">260dp</dimen> + <dimen name="low_light_clock_charging_text_size">14sp</dimen> + <dimen name="low_light_clock_charging_text_min_height">48dp</dimen> + <integer name="low_light_clock_charging_text_font_weight">500</integer> + + <dimen name="low_light_clock_translate_animation_offset">40dp</dimen> + <integer name="low_light_clock_translate_animation_duration_ms">1167</integer> + <integer name="low_light_clock_alpha_animation_in_start_delay_ms">233</integer> + <integer name="low_light_clock_alpha_animation_duration_ms">250</integer> + <!-- Distance that the full shade transition takes in order for media to fully transition to the shade --> <dimen name="lockscreen_shade_media_transition_distance">120dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index c95c6dd89353..fa6a41a74ca9 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -176,17 +176,11 @@ <style name="TextAppearance.QS.Status"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">?attr/onSurface</item> + <item name="android:textColor">@color/shade_header_text_color</item> <item name="android:textSize">14sp</item> <item name="android:letterSpacing">0.01</item> </style> - <style name="TextAppearance.QS.Status.Carriers" /> - - <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> - <item name="android:textColor">?attr/onSurfaceVariant</item> - </style> - <style name="TextAppearance.QS.Status.Build"> <item name="android:textColor">?attr/onSurfaceVariant</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl index 011458859db4..b43ffc530289 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl @@ -24,7 +24,7 @@ import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; // Next ID: 39 -oneway interface IOverviewProxy { +oneway interface ILauncherProxy { void onActiveNavBarRegionChanges(in Region activeRegion) = 11; @@ -140,7 +140,7 @@ oneway interface IOverviewProxy { void appTransitionPending(boolean pending) = 34; /** - * Sent right after OverviewProxy calls unbindService() on the TouchInteractionService. + * Sent right after LauncherProxyService calls unbindService() on the TouchInteractionService. * TouchInteractionService is expected to send the reply once it has finished cleaning up. */ void onUnbind(IRemoteCallback reply) = 35; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index e332280bc31a..1f6bea18d53a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -69,10 +69,10 @@ interface ISystemUiProxy { /** * Indicates that the given Assist invocation types should be handled by Launcher via - * OverviewProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI. + * LauncherProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI. * * @param invocationTypes The invocation types that will henceforth be handled via - * OverviewProxy (Launcher); other invocation types should be handled by SysUI. + * LauncherProxy (Launcher); other invocation types should be handled by SysUI. */ oneway void setAssistantOverridesRequested(in int[] invocationTypes) = 53; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 7f176de547bc..0e9d8fec9717 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; + import static com.android.systemui.Flags.pinInputFieldStyledFocusState; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -164,6 +166,8 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB layoutParams.height = (int) getResources().getDimension( R.dimen.keyguard_pin_field_height); } + + mPasswordEntry.sendAccessibilityEvent(TYPE_VIEW_FOCUSED); } private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 40c1f0f9895d..4a8e4ed3f6f1 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -39,7 +39,7 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; @@ -129,7 +129,7 @@ public class Dependency { @Inject Lazy<MetricsLogger> mMetricsLogger; @Inject Lazy<UiOffloadThread> mUiOffloadThread; @Inject Lazy<LightBarController> mLightBarController; - @Inject Lazy<OverviewProxyService> mOverviewProxyService; + @Inject Lazy<LauncherProxyService> mLauncherProxyService; @Inject Lazy<NavigationModeController> mNavBarModeController; @Inject Lazy<NavigationBarController> mNavigationBarController; @Inject Lazy<StatusBarStateController> mStatusBarStateController; @@ -175,7 +175,7 @@ public class Dependency { mProviders.put(MetricsLogger.class, mMetricsLogger::get); mProviders.put(UiOffloadThread.class, mUiOffloadThread::get); mProviders.put(LightBarController.class, mLightBarController::get); - mProviders.put(OverviewProxyService.class, mOverviewProxyService::get); + mProviders.put(LauncherProxyService.class, mLauncherProxyService::get); mProviders.put(NavigationModeController.class, mNavBarModeController::get); mProviders.put(NavigationBarController.class, mNavigationBarController::get); mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java index 5cba464fc24c..5482c3d3ea18 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java @@ -50,7 +50,7 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.settings.SecureSettings; @@ -79,7 +79,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks private final Executor mExecutor; private final AccessibilityManager mAccessibilityManager; private final CommandQueue mCommandQueue; - private final OverviewProxyService mOverviewProxyService; + private final LauncherProxyService mLauncherProxyService; private final DisplayTracker mDisplayTracker; private final AccessibilityLogger mA11yLogger; @@ -225,13 +225,13 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks public MagnificationImpl(Context context, @Main Handler mainHandler, @Main Executor executor, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, - SysUiState sysUiState, OverviewProxyService overviewProxyService, + SysUiState sysUiState, LauncherProxyService launcherProxyService, SecureSettings secureSettings, DisplayTracker displayTracker, DisplayManager displayManager, AccessibilityLogger a11yLogger, IWindowManager iWindowManager, AccessibilityManager accessibilityManager, ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) { this(context, mainHandler.getLooper(), executor, commandQueue, - modeSwitchesController, sysUiState, overviewProxyService, secureSettings, + modeSwitchesController, sysUiState, launcherProxyService, secureSettings, displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager, viewCaptureAwareWindowManager); } @@ -239,7 +239,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks @VisibleForTesting public MagnificationImpl(Context context, Looper looper, @Main Executor executor, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, - SysUiState sysUiState, OverviewProxyService overviewProxyService, + SysUiState sysUiState, LauncherProxyService launcherProxyService, SecureSettings secureSettings, DisplayTracker displayTracker, DisplayManager displayManager, AccessibilityLogger a11yLogger, IWindowManager iWindowManager, @@ -258,7 +258,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks mCommandQueue = commandQueue; mModeSwitchesController = modeSwitchesController; mSysUiState = sysUiState; - mOverviewProxyService = overviewProxyService; + mLauncherProxyService = launcherProxyService; mDisplayTracker = displayTracker; mA11yLogger = a11yLogger; mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context, @@ -279,7 +279,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks @Override public void start() { mCommandQueue.addCallback(this); - mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { + mLauncherProxyService.addCallback(new LauncherProxyService.LauncherProxyListener() { @Override public void onConnectionChanged(boolean isConnected) { if (isConnected) { diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 939d96e67f8f..da1c1bc49d23 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -37,7 +37,7 @@ import com.android.systemui.assist.ui.DefaultUiController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; @@ -142,7 +142,7 @@ public class AssistManager { protected final Context mContext; private final AssistDisclosure mAssistDisclosure; private final PhoneStateMonitor mPhoneStateMonitor; - private final OverviewProxyService mOverviewProxyService; + private final LauncherProxyService mLauncherProxyService; private final UiController mUiController; protected final Lazy<SysUiState> mSysUiState; protected final AssistLogger mAssistLogger; @@ -176,7 +176,7 @@ public class AssistManager { private final CommandQueue mCommandQueue; protected final AssistUtils mAssistUtils; - // Invocation types that should be sent over OverviewProxy instead of handled here. + // Invocation types that should be sent over LauncherProxy instead of handled here. private int[] mAssistOverrideInvocationTypes; @Inject @@ -186,7 +186,7 @@ public class AssistManager { AssistUtils assistUtils, CommandQueue commandQueue, PhoneStateMonitor phoneStateMonitor, - OverviewProxyService overviewProxyService, + LauncherProxyService launcherProxyService, Lazy<SysUiState> sysUiState, DefaultUiController defaultUiController, AssistLogger assistLogger, @@ -203,7 +203,7 @@ public class AssistManager { mCommandQueue = commandQueue; mAssistUtils = assistUtils; mAssistDisclosure = new AssistDisclosure(context, uiHandler, viewCaptureAwareWindowManager); - mOverviewProxyService = overviewProxyService; + mLauncherProxyService = launcherProxyService; mPhoneStateMonitor = phoneStateMonitor; mAssistLogger = assistLogger; mUserTracker = userTracker; @@ -220,7 +220,7 @@ public class AssistManager { mSysUiState = sysUiState; - mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { + mLauncherProxyService.addCallback(new LauncherProxyService.LauncherProxyListener() { @Override public void onAssistantProgress(float progress) { // Progress goes from 0 to 1 to indicate how close the assist gesture is to @@ -288,14 +288,14 @@ public class AssistManager { } if (shouldOverrideAssist(args)) { try { - if (mOverviewProxyService.getProxy() == null) { - Log.w(TAG, "No OverviewProxyService to invoke assistant override"); + if (mLauncherProxyService.getProxy() == null) { + Log.w(TAG, "No LauncherProxyService to invoke assistant override"); return; } - mOverviewProxyService.getProxy().onAssistantOverrideInvoked( + mLauncherProxyService.getProxy().onAssistantOverrideInvoked( args.getInt(INVOCATION_TYPE_KEY)); } catch (RemoteException e) { - Log.w(TAG, "Unable to invoke assistant via OverviewProxyService override", e); + Log.w(TAG, "Unable to invoke assistant via LauncherProxyService override", e); } return; } @@ -333,7 +333,7 @@ public class AssistManager { return shouldOverrideAssist(invocationType); } - /** @return true if the invocation type should be handled by OverviewProxy instead of SysUI. */ + /** @return true if the invocation type should be handled by LauncherProxy instead of SysUI. */ public boolean shouldOverrideAssist(int invocationType) { return mAssistOverrideInvocationTypes != null && Arrays.stream(mAssistOverrideInvocationTypes).anyMatch( @@ -342,7 +342,7 @@ public class AssistManager { /** * @param invocationTypes The invocation types that will henceforth be handled via - * OverviewProxy (Launcher); other invocation types should be handled by + * LauncherProxy (Launcher); other invocation types should be handled by * this class. */ public void setAssistantOverridesRequested(int[] invocationTypes) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java new file mode 100644 index 000000000000..2e1b5ad177b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java @@ -0,0 +1,101 @@ +/* + * 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.communal; + +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.dagger.qualifiers.Application; +import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import kotlinx.coroutines.CoroutineScope; + +import javax.inject.Inject; + +/** + * Condition which estimates device inactivity in order to avoid launching a full-screen activity + * while the user is actively using the device. + */ +public class DeviceInactiveCondition extends Condition { + private final KeyguardStateController mKeyguardStateController; + private final WakefulnessLifecycle mWakefulnessLifecycle; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final KeyguardStateController.Callback mKeyguardStateCallback = + new KeyguardStateController.Callback() { + @Override + public void onKeyguardShowingChanged() { + updateState(); + } + }; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + @Override + public void onStartedGoingToSleep() { + updateState(); + } + }; + private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onDreamingStateChanged(boolean dreaming) { + updateState(); + } + }; + + @Inject + public DeviceInactiveCondition(@Application CoroutineScope scope, + KeyguardStateController keyguardStateController, + WakefulnessLifecycle wakefulnessLifecycle, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + super(scope); + mKeyguardStateController = keyguardStateController; + mWakefulnessLifecycle = wakefulnessLifecycle; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + } + + @Override + protected void start() { + updateState(); + mKeyguardStateController.addCallback(mKeyguardStateCallback); + mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + } + + @Override + protected void stop() { + mKeyguardStateController.removeCallback(mKeyguardStateCallback); + mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback); + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); + } + + @Override + protected int getStartStrategy() { + return START_EAGERLY; + } + + private void updateState() { + final boolean asleep = + mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP + || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP; + updateCondition(asleep || mKeyguardStateController.isShowing() + || mKeyguardUpdateMonitor.isDreaming()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index c02784dfab1b..fe5a82cb5b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -81,6 +81,7 @@ import com.android.systemui.keyguard.ui.composable.LockscreenContent; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; +import com.android.systemui.lowlightclock.dagger.LowLightModule; import com.android.systemui.mediaprojection.MediaProjectionModule; import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitiesModule; import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule; @@ -285,7 +286,8 @@ import javax.inject.Named; UserModule.class, UtilModule.class, NoteTaskModule.class, - WalletModule.class + WalletModule.class, + LowLightModule.class }, subcomponents = { ComplicationComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index faab31eff4f7..15f73ee0eda6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -48,6 +48,8 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig; import com.android.systemui.res.R; import com.android.systemui.touch.TouchInsetManager; +import com.google.android.systemui.lowlightclock.LowLightClockDreamService; + import dagger.Binds; import dagger.BindsOptionalOf; import dagger.Module; @@ -238,15 +240,24 @@ public interface DreamModule { ComponentName bindsLowLightClockDream(); /** + * Provides low light clock dream service component. + */ + @Provides + @Named(LOW_LIGHT_CLOCK_DREAM) + static ComponentName providesLowLightClockDream(Context context) { + return new ComponentName(context, LowLightClockDreamService.class); + } + + /** * Provides the component name of the low light dream, or null if not configured. */ @Provides @Nullable @Named(LOW_LIGHT_DREAM_SERVICE) static ComponentName providesLowLightDreamService(Context context, - @Named(LOW_LIGHT_CLOCK_DREAM) Optional<ComponentName> clockDream) { - if (Flags.lowLightClockDream() && clockDream.isPresent()) { - return clockDream.get(); + @Named(LOW_LIGHT_CLOCK_DREAM) ComponentName clockDream) { + if (Flags.lowLightClockDream()) { + return clockDream; } String lowLightDreamComponent = context.getResources().getString( diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt index d7a4dba3188a..9bdf812713d7 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt @@ -37,8 +37,8 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository -import com.android.systemui.recents.OverviewProxyService -import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.recents.LauncherProxyService +import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Clock import java.time.Instant @@ -67,7 +67,7 @@ constructor( private val contextualEducationInteractor: ContextualEducationInteractor, private val userInputDeviceRepository: UserInputDeviceRepository, private val tutorialRepository: TutorialSchedulerRepository, - private val overviewProxyService: OverviewProxyService, + private val launcherProxyService: LauncherProxyService, private val metricsLogger: ContextualEducationMetricsLogger, @EduClock private val clock: Clock, ) : CoreStartable { @@ -100,8 +100,8 @@ constructor( val educationTriggered = _educationTriggered.asStateFlow() private val statsUpdateRequests: Flow<StatsUpdateRequest> = conflatedCallbackFlow { - val listener: OverviewProxyListener = - object : OverviewProxyListener { + val listener: LauncherProxyListener = + object : LauncherProxyListener { override fun updateContextualEduStats( isTrackpadGesture: Boolean, gestureType: GestureType, @@ -113,8 +113,8 @@ constructor( } } - overviewProxyService.addCallback(listener) - awaitClose { overviewProxyService.removeCallback(listener) } + launcherProxyService.addCallback(listener) + awaitClose { launcherProxyService.removeCallback(listener) } } private val gestureModelMap: Flow<Map<GestureType, GestureEduModel>> = diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt index e5c638cbdfba..d355f761e5ae 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt @@ -32,18 +32,19 @@ import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestRe import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.SUCCESS import com.android.systemui.settings.UserTracker +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext -import javax.inject.Inject -import kotlin.coroutines.CoroutineContext @SysUISingleton class CustomInputGesturesRepository @Inject -constructor(private val userTracker: UserTracker, - @Background private val bgCoroutineContext: CoroutineContext) -{ +constructor( + private val userTracker: UserTracker, + @Background private val bgCoroutineContext: CoroutineContext, +) { private val userContext: Context get() = userTracker.createCurrentUserContext(userTracker.userContext) @@ -55,8 +56,7 @@ constructor(private val userTracker: UserTracker, private val _customInputGesture = MutableStateFlow<List<InputGestureData>>(emptyList()) - val customInputGestures = - _customInputGesture.onStart { refreshCustomInputGestures() } + val customInputGestures = _customInputGesture.onStart { refreshCustomInputGestures() } fun refreshCustomInputGestures() { setCustomInputGestures(inputGestures = retrieveCustomInputGestures()) @@ -72,24 +72,24 @@ constructor(private val userTracker: UserTracker, } else emptyList() } - suspend fun addCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult { + suspend fun addCustomInputGesture( + inputGesture: InputGestureData + ): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext) { when (val result = inputManager.addCustomInputGesture(inputGesture)) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { refreshCustomInputGestures() SUCCESS } - CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS -> - ERROR_RESERVED_COMBINATION + CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS -> ERROR_RESERVED_COMBINATION - CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE -> - ERROR_RESERVED_COMBINATION + CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE -> ERROR_RESERVED_COMBINATION else -> { Log.w( TAG, "Attempted to add inputGesture: $inputGesture " + - "but ran into an error with code: $result", + "but ran into an error with code: $result", ) ERROR_OTHER } @@ -97,11 +97,11 @@ constructor(private val userTracker: UserTracker, } } - suspend fun deleteCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult { - return withContext(bgCoroutineContext){ - when ( - val result = inputManager.removeCustomInputGesture(inputGesture) - ) { + suspend fun deleteCustomInputGesture( + inputGesture: InputGestureData + ): ShortcutCustomizationRequestResult { + return withContext(bgCoroutineContext) { + when (val result = inputManager.removeCustomInputGesture(inputGesture)) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { refreshCustomInputGestures() SUCCESS @@ -110,7 +110,7 @@ constructor(private val userTracker: UserTracker, Log.w( TAG, "Attempted to delete inputGesture: $inputGesture " + - "but ran into an error with code: $result", + "but ran into an error with code: $result", ) ERROR_OTHER } @@ -134,7 +134,10 @@ constructor(private val userTracker: UserTracker, } } + suspend fun getInputGestureByTrigger(trigger: InputGestureData.Trigger): InputGestureData? = + withContext(bgCoroutineContext) { inputManager.getInputGesture(trigger) } + private companion object { private const val TAG = "CustomInputGesturesRepository" } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt index 18ca877775df..6ae948d2da2e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.hardware.input.InputGestureData import android.hardware.input.InputGestureData.Builder +import android.hardware.input.InputGestureData.Trigger import android.hardware.input.InputGestureData.createKeyTrigger import android.hardware.input.InputManager import android.hardware.input.KeyGestureEvent.KeyGestureType @@ -175,6 +176,11 @@ constructor( return customInputGesturesRepository.resetAllCustomInputGestures() } + suspend fun isSelectedKeyCombinationAvailable(): Boolean { + val trigger = buildTriggerFromSelectedKeyCombination() ?: return false + return customInputGesturesRepository.getInputGestureByTrigger(trigger) == null + } + private fun Builder.addKeyGestureTypeForShortcutBeingCustomized(): Builder { val keyGestureType = getKeyGestureTypeForShortcutBeingCustomized() @@ -222,7 +228,10 @@ constructor( ) } - private fun Builder.addTriggerFromSelectedKeyCombination(): Builder { + private fun Builder.addTriggerFromSelectedKeyCombination(): Builder = + setTrigger(buildTriggerFromSelectedKeyCombination()) + + private fun buildTriggerFromSelectedKeyCombination(): Trigger? { val selectedKeyCombination = _selectedKeyCombination.value if (selectedKeyCombination?.keyCode == null) { Log.w( @@ -230,16 +239,14 @@ constructor( "User requested to set shortcut but selected key combination is " + "$selectedKeyCombination", ) - return this + return null } - return setTrigger( - createKeyTrigger( - /* keycode = */ selectedKeyCombination.keyCode, - /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers( - selectedKeyCombination.modifiers - ), - ) + return createKeyTrigger( + /* keycode= */ selectedKeyCombination.keyCode, + /* modifierState= */ shortcutCategoriesUtils.removeUnsupportedModifiers( + selectedKeyCombination.modifiers + ), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt index ef242678a8ac..1a62517ad01d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt @@ -53,4 +53,7 @@ constructor(private val customShortcutRepository: CustomShortcutCategoriesReposi suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult { return customShortcutRepository.resetAllCustomShortcuts() } + + suspend fun isSelectedKeyCombinationAvailable(): Boolean = + customShortcutRepository.isSelectedKeyCombinationAvailable() } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt index bd3d46d09f5e..54e27a61ac78 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.phone.create import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch class ShortcutCustomizationDialogStarter @@ -57,20 +58,25 @@ constructor( private val viewModel = viewModelFactory.create() override suspend fun onActivated(): Nothing { - viewModel.shortcutCustomizationUiState.collect { uiState -> - when (uiState) { - is AddShortcutDialog, - is DeleteShortcutDialog, - is ResetShortcutDialog -> { - if (dialog == null) { - dialog = createDialog().also { it.show() } + coroutineScope { + launch { + viewModel.shortcutCustomizationUiState.collect { uiState -> + when (uiState) { + is AddShortcutDialog, + is DeleteShortcutDialog, + is ResetShortcutDialog -> { + if (dialog == null) { + dialog = createDialog().also { it.show() } + } + } + is ShortcutCustomizationUiState.Inactive -> { + dialog?.dismiss() + dialog = null + } } } - is ShortcutCustomizationUiState.Inactive -> { - dialog?.dismiss() - dialog = null - } } + launch { viewModel.activate() } } awaitCancellation() } @@ -101,6 +107,7 @@ constructor( onConfirmResetShortcut = { coroutineScope.launch { viewModel.resetAllCustomShortcuts() } }, + onClearSelectedKeyCombination = { viewModel.clearSelectedKeyCombination() }, ) setDialogProperties(dialog, uiState) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt index fa03883e2a35..ea36a10fb01a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt @@ -24,7 +24,6 @@ import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting import androidx.compose.foundation.layout.width -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -36,6 +35,7 @@ import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelperBottomSheet import com.android.systemui.keyboard.shortcut.ui.composable.getWidth import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel +import com.android.systemui.lifecycle.rememberActivated import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory @@ -51,14 +51,13 @@ class ShortcutHelperDialogStarter constructor( @Application private val applicationScope: CoroutineScope, private val shortcutHelperViewModel: ShortcutHelperViewModel, - shortcutCustomizationDialogStarterFactory: ShortcutCustomizationDialogStarter.Factory, + private val shortcutCustomizationDialogStarterFactory: + ShortcutCustomizationDialogStarter.Factory, private val dialogFactory: SystemUIDialogFactory, private val activityStarter: ActivityStarter, ) : CoreStartable { @VisibleForTesting var dialog: Dialog? = null - private val shortcutCustomizationDialogStarter = - shortcutCustomizationDialogStarterFactory.create() override fun start() { shortcutHelperViewModel.shouldShow @@ -77,7 +76,10 @@ constructor( content = { dialog -> val shortcutsUiState by shortcutHelperViewModel.shortcutsUiState.collectAsStateWithLifecycle() - LaunchedEffect(Unit) { shortcutCustomizationDialogStarter.activate() } + val shortcutCustomizationDialogStarter = + rememberActivated(traceName = "shortcutCustomizationDialogStarter") { + shortcutCustomizationDialogStarterFactory.create() + } ShortcutHelper( modifier = Modifier.width(getWidth()), shortcutsUiState = shortcutsUiState, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt index 9d43c48ee274..66e45056989d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt @@ -18,14 +18,10 @@ package com.android.systemui.keyboard.shortcut.ui.composable import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn @@ -40,13 +36,15 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.ErrorOutline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.focus.focusRequester @@ -57,6 +55,7 @@ import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.LiveRegionMode @@ -81,6 +80,7 @@ fun ShortcutCustomizationDialog( onConfirmSetShortcut: () -> Unit, onConfirmDeleteShortcut: () -> Unit, onConfirmResetShortcut: () -> Unit, + onClearSelectedKeyCombination: () -> Unit, ) { when (uiState) { is ShortcutCustomizationUiState.AddShortcutDialog -> { @@ -90,6 +90,7 @@ fun ShortcutCustomizationDialog( onShortcutKeyCombinationSelected, onCancel, onConfirmSetShortcut, + onClearSelectedKeyCombination, ) } is ShortcutCustomizationUiState.DeleteShortcutDialog -> { @@ -111,6 +112,7 @@ private fun AddShortcutDialog( onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean, onCancel: () -> Unit, onConfirmSetShortcut: () -> Unit, + onClearSelectedKeyCombination: () -> Unit, ) { Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { Title(uiState.shortcutLabel) @@ -126,6 +128,7 @@ private fun AddShortcutDialog( onShortcutKeyCombinationSelected = onShortcutKeyCombinationSelected, pressedKeys = uiState.pressedKeys, onConfirmSetShortcut = onConfirmSetShortcut, + onClearSelectedKeyCombination = onClearSelectedKeyCombination, ) ErrorMessageContainer(uiState.errorMessage) DialogButtons( @@ -249,10 +252,11 @@ private fun ErrorMessageContainer(errorMessage: String) { lineHeight = 20.sp, fontWeight = FontWeight.W500, color = MaterialTheme.colorScheme.error, - modifier = Modifier.padding(start = 24.dp).width(252.dp).semantics { - contentDescription = errorMessage - liveRegion = LiveRegionMode.Polite - }, + modifier = + Modifier.padding(start = 24.dp).width(252.dp).semantics { + contentDescription = errorMessage + liveRegion = LiveRegionMode.Polite + }, ) } } @@ -264,72 +268,82 @@ private fun SelectedKeyCombinationContainer( onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean, pressedKeys: List<ShortcutKey>, onConfirmSetShortcut: () -> Unit, + onClearSelectedKeyCombination: () -> Unit, ) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val outlineColor = - if (!isFocused) MaterialTheme.colorScheme.outline - else if (shouldShowError) MaterialTheme.colorScheme.error - else MaterialTheme.colorScheme.primary val focusRequester = remember { FocusRequester() } - + val focusManager = LocalFocusManager.current LaunchedEffect(Unit) { focusRequester.requestFocus() } - ClickableShortcutSurface( - onClick = {}, - color = Color.Transparent, - shape = RoundedCornerShape(50.dp), + OutlinedInputField( modifier = Modifier.padding(all = 16.dp) .sizeIn(minWidth = 332.dp, minHeight = 56.dp) - .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp)) + .focusRequester(focusRequester) + .focusProperties { canFocus = true } .onPreviewKeyEvent { keyEvent -> val keyEventProcessed = onShortcutKeyCombinationSelected(keyEvent) - if ( - !keyEventProcessed && - keyEvent.key == Key.Enter && - keyEvent.type == KeyEventType.KeyUp - ) { - onConfirmSetShortcut() + if (keyEventProcessed) { true - } else keyEventProcessed - } - .focusProperties { canFocus = true } // enables keyboard focus when in touch mode - .focusRequester(focusRequester), - interactionSource = interactionSource, - ) { - Row( - modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - if (pressedKeys.isEmpty()) { - PressKeyPrompt() + } else { + if (keyEvent.type == KeyEventType.KeyUp) { + when (keyEvent.key) { + Key.Enter -> { + onConfirmSetShortcut() + return@onPreviewKeyEvent true + } + Key.Backspace -> { + onClearSelectedKeyCombination() + return@onPreviewKeyEvent true + } + Key.DirectionDown -> { + focusManager.moveFocus(FocusDirection.Down) + return@onPreviewKeyEvent true + } + else -> return@onPreviewKeyEvent false + } + } else false + } + }, + trailingIcon = { ErrorIcon(shouldShowError) }, + isError = shouldShowError, + placeholder = { PressKeyPrompt() }, + content = + if (pressedKeys.isNotEmpty()) { + { PressedKeysTextContainer(pressedKeys) } } else { - PressedKeysTextContainer(pressedKeys) - } - Spacer(modifier = Modifier.weight(1f)) - if (shouldShowError) { - Icon( - imageVector = Icons.Default.ErrorOutline, - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.error, - ) - } - } + null + }, + ) +} + +@Composable +private fun ErrorIcon(shouldShowError: Boolean) { + if (shouldShowError) { + Icon( + imageVector = Icons.Default.ErrorOutline, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.error, + ) } } @Composable -private fun RowScope.PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) { - pressedKeys.forEachIndexed { keyIndex, key -> - if (keyIndex > 0) { - ShortcutKeySeparator() - } - if (key is ShortcutKey.Text) { - ShortcutTextKey(key) - } else if (key is ShortcutKey.Icon) { - ShortcutIconKey(key) +private fun PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) { + Row( + modifier = + Modifier.semantics(mergeDescendants = true) { liveRegion = LiveRegionMode.Polite }, + verticalAlignment = Alignment.CenterVertically, + ) { + pressedKeys.forEachIndexed { keyIndex, key -> + if (keyIndex > 0) { + ShortcutKeySeparator() + } + if (key is ShortcutKey.Text) { + ShortcutTextKey(key) + } else if (key is ShortcutKey.Icon) { + ShortcutIconKey(key) + } } } } @@ -346,7 +360,7 @@ private fun ShortcutKeySeparator() { } @Composable -private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) { +private fun ShortcutIconKey(key: ShortcutKey.Icon) { Icon( painter = when (key) { @@ -354,7 +368,7 @@ private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) { is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable) }, contentDescription = null, - modifier = Modifier.align(Alignment.CenterVertically).height(24.dp), + modifier = Modifier.height(24.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } @@ -405,7 +419,7 @@ private fun Description(text: String) { .width(316.dp) .wrapContentSize(Alignment.Center), color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) } @@ -473,3 +487,31 @@ private fun PlusIconContainer() { modifier = Modifier.padding(vertical = 12.dp).size(24.dp).wrapContentSize(Alignment.Center), ) } + +@Composable +private fun OutlinedInputField( + content: @Composable (() -> Unit)?, + placeholder: @Composable () -> Unit, + trailingIcon: @Composable () -> Unit, + isError: Boolean, + modifier: Modifier = Modifier, +) { + OutlinedTextField( + value = "", + onValueChange = {}, + placeholder = if (content == null) placeholder else null, + prefix = content, + singleLine = true, + modifier = modifier, + trailingIcon = trailingIcon, + colors = + OutlinedTextFieldDefaults.colors() + .copy( + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.outline, + errorIndicatorColor = MaterialTheme.colorScheme.error, + ), + shape = RoundedCornerShape(50.dp), + isError = isError, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt index 915a66c43a12..f4ba99c6a394 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt @@ -28,16 +28,17 @@ import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestRe import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog +import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.res.R import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update class ShortcutCustomizationViewModel @@ -45,26 +46,12 @@ class ShortcutCustomizationViewModel constructor( private val context: Context, private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor, -) { +) : ExclusiveActivatable() { private var keyDownEventCache: KeyEvent? = null private val _shortcutCustomizationUiState = MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive) - val shortcutCustomizationUiState = - shortcutCustomizationInteractor.pressedKeys - .map { keys -> - // Note that Action Key is excluded as it's already displayed on the UI - keys.filter { - it != shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey() - } - } - .combine(_shortcutCustomizationUiState) { keys, uiState -> - if (uiState is AddShortcutDialog) { - uiState.copy(pressedKeys = keys) - } else { - uiState - } - } + val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow() fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { shortcutCustomizationInteractor.onCustomizationRequested(requestInfo) @@ -92,7 +79,7 @@ constructor( fun onDialogDismissed() { _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive shortcutCustomizationInteractor.onCustomizationRequested(null) - shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null) + clearSelectedKeyCombination() } fun onShortcutKeyCombinationSelected(keyEvent: KeyEvent): Boolean { @@ -112,7 +99,6 @@ constructor( suspend fun onSetShortcut() { val result = shortcutCustomizationInteractor.confirmAndSetShortcutCurrentlyBeingCustomized() - _shortcutCustomizationUiState.update { uiState -> when (result) { ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive @@ -158,6 +144,10 @@ constructor( } } + fun clearSelectedKeyCombination() { + shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null) + } + private fun getUiStateWithErrorMessage( uiState: ShortcutCustomizationUiState, errorMessage: String, @@ -180,11 +170,41 @@ constructor( keyDownEventCache = null } + private suspend fun isSelectedKeyCombinationAvailable() = + shortcutCustomizationInteractor.isSelectedKeyCombinationAvailable() + @AssistedFactory interface Factory { fun create(): ShortcutCustomizationViewModel } + override suspend fun onActivated(): Nothing { + shortcutCustomizationInteractor.pressedKeys.collect { + val keys = filterDefaultCustomShortcutModifierKey(it) + val errorMessage = getErrorMessageForPressedKeys(keys) + + _shortcutCustomizationUiState.update { uiState -> + if (uiState is AddShortcutDialog) { + uiState.copy(pressedKeys = keys, errorMessage = errorMessage) + } else { + uiState + } + } + } + } + + private suspend fun getErrorMessageForPressedKeys(keys: List<ShortcutKey>): String { + return if (keys.isEmpty() or isSelectedKeyCombinationAvailable()) { + "" + } + else { + context.getString(R.string.shortcut_customizer_key_combination_in_use_error_message) + } + } + + private fun filterDefaultCustomShortcutModifierKey(keys: List<ShortcutKey>) = + keys.filter { it != shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey() } + companion object { private val SUPPORTED_MODIFIERS = listOf( 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 3565b612a3c9..c5d40a0dcf30 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 @@ -48,7 +48,6 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext @OptIn(FlowPreview::class) @@ -92,6 +91,7 @@ constructor( listenForHubToAlternateBouncer() listenForHubToOccluded() listenForHubToGone() + listenForHubToDreaming() } override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { @@ -177,6 +177,24 @@ constructor( } } + private fun listenForHubToDreaming() { + if (!communalSettingsInteractor.isV2FlagEnabled()) { + return + } + + scope.launch { + keyguardInteractor.isAbleToDream + .filterRelevantKeyguardStateAnd { isAbleToDream -> isAbleToDream } + .collect { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + loggingReason = "hub to dreaming", + keyguardState = KeyguardState.DREAMING, + ) + } + } + } + private fun listenForHubToOccluded() { if (KeyguardWmStateRefactor.isEnabled) { scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index 184f30237e8d..6a354821f628 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -24,7 +24,6 @@ import com.android.systemui.Flags.transitionRaceCondition import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState @@ -331,17 +330,9 @@ constructor( * clock/smartspace/notif icons are visible. */ val aodVisibility: Flow<Boolean> = - combine( - keyguardInteractor.isDozing, - keyguardInteractor.isAodAvailable, - keyguardInteractor.biometricUnlockState, - ) { isDozing, isAodAvailable, biometricUnlockState -> - // AOD is visible if we're dozing, unless we are wake and unlocking (where we go - // directly from AOD to unlocked while dozing). - isDozing && - isAodAvailable && - !BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) - } + transitionInteractor + .transitionValue(KeyguardState.AOD) + .map { it == 1f } .distinctUntilChanged() companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt index eb005f226cf1..454ba9af5745 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt @@ -76,9 +76,8 @@ constructor( private var manualUnlockAmount: Float? = null /** - * Called from [OverviewProxyService] to provide us with the launcher unlock animation - * controller, which can be used to start and update the unlock animation in the launcher - * process. + * Called from Launcher to provide us with the launcher unlock animation controller, which can + * be used to start and update the unlock animation in the launcher process. */ override fun setLauncherUnlockController( activityClass: String, @@ -117,7 +116,7 @@ constructor( launcher.prepareForUnlock( false, Rect(), - 0 + 0, ) // TODO(b/293894758): Add smartspace animation support. } } @@ -134,14 +133,14 @@ constructor( Log.e( TAG, "Called prepareForUnlock(), but not playUnlockAnimation(). " + - "Failing-safe by calling setUnlockAmount(1f)" + "Failing-safe by calling setUnlockAmount(1f)", ) setUnlockAmount(1f, forceIfAnimating = true) } else if (manualUnlockSetButNotFullyVisible) { Log.e( TAG, "Unlock has ended, but manual unlock amount != 1f. " + - "Failing-safe by calling setUnlockAmount(1f)" + "Failing-safe by calling setUnlockAmount(1f)", ) setUnlockAmount(1f, forceIfAnimating = true) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt index b531c7fa49ec..a6d15b96547d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt @@ -100,7 +100,11 @@ constructor( override val windowBlurRadius: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsExpanded = - transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), + if (Flags.notificationShadeBlur()) { + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + } else { + emptyFlow() + }, flowWhenShadeIsNotExpanded = transitionAnimation.sharedFlow( duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index 89dcbf6aa52b..e25e4e5af425 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -107,7 +107,11 @@ constructor( override val windowBlurRadius: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsExpanded = - transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), + if (Flags.notificationShadeBlur()) { + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + } else { + emptyFlow() + }, flowWhenShadeIsNotExpanded = transitionAnimation.sharedFlow( duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt index 4d3e27265cea..3c126aa23fef 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -26,12 +27,16 @@ import com.android.systemui.keyguard.ui.transitions.BlurConfig import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow @SysUISingleton class OccludedToPrimaryBouncerTransitionViewModel @Inject -constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFlow) : - PrimaryBouncerTransition { +constructor( + shadeDependentFlows: ShadeDependentFlows, + blurConfig: BlurConfig, + animationFlow: KeyguardTransitionAnimationFlow, +) : PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( @@ -41,8 +46,21 @@ constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFl .setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, PRIMARY_BOUNCER)) override val windowBlurRadius: Flow<Float> = - transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = + if (Flags.notificationShadeBlur()) { + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + } else { + emptyFlow() + }, + flowWhenShadeIsNotExpanded = + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), + ) override val notificationBlurRadius: Flow<Float> = - transitionAnimation.immediatelyTransitionTo(0.0f) + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), + flowWhenShadeIsNotExpanded = emptyFlow(), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt index c53a408a88e1..9b01803f1fd5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -32,6 +33,7 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow /** * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to @@ -78,7 +80,11 @@ constructor( override val windowBlurRadius: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsExpanded = - transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), + if (Flags.notificationShadeBlur()) { + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + } else { + emptyFlow() + }, flowWhenShadeIsNotExpanded = transitionAnimation.sharedFlow( duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt index fe1708efea2f..0f0e7b6faa66 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -26,12 +27,16 @@ import com.android.systemui.keyguard.ui.transitions.BlurConfig import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow @SysUISingleton class PrimaryBouncerToOccludedTransitionViewModel @Inject -constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFlow) : - PrimaryBouncerTransition { +constructor( + shadeDependentFlows: ShadeDependentFlows, + blurConfig: BlurConfig, + animationFlow: KeyguardTransitionAnimationFlow, +) : PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( @@ -41,7 +46,16 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio .setupWithoutSceneContainer(edge = Edge.create(PRIMARY_BOUNCER, OCCLUDED)) override val windowBlurRadius: Flow<Float> = - transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx) + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = + if (Flags.notificationShadeBlur()) { + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + } else { + emptyFlow() + }, + flowWhenShadeIsNotExpanded = + transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx), + ) override val notificationBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0.0f) diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt new file mode 100644 index 000000000000..ece97bd27df7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt @@ -0,0 +1,137 @@ +/* + * 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.lowlightclock + +import android.annotation.IntDef +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.util.Log +import com.android.systemui.Dumpable +import com.android.systemui.lowlightclock.dagger.LowLightModule.LIGHT_SENSOR +import com.android.systemui.util.sensors.AsyncSensorManager +import java.io.PrintWriter +import java.util.Optional +import javax.inject.Inject +import javax.inject.Named + +/** + * Monitors ambient light signals, applies a debouncing algorithm, and produces the current ambient + * light mode. + * + * @property algorithm the debounce algorithm which transforms light sensor events into an ambient + * light mode. + * @property sensorManager the sensor manager used to register sensor event updates. + */ +class AmbientLightModeMonitor +@Inject +constructor( + private val algorithm: Optional<DebounceAlgorithm>, + private val sensorManager: AsyncSensorManager, + @Named(LIGHT_SENSOR) private val lightSensor: Optional<Sensor>, +) : Dumpable { + companion object { + private const val TAG = "AmbientLightModeMonitor" + private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + + const val AMBIENT_LIGHT_MODE_LIGHT = 0 + const val AMBIENT_LIGHT_MODE_DARK = 1 + const val AMBIENT_LIGHT_MODE_UNDECIDED = 2 + } + + // Represents all ambient light modes. + @Retention(AnnotationRetention.SOURCE) + @IntDef(AMBIENT_LIGHT_MODE_LIGHT, AMBIENT_LIGHT_MODE_DARK, AMBIENT_LIGHT_MODE_UNDECIDED) + annotation class AmbientLightMode + + /** + * Start monitoring the current ambient light mode. + * + * @param callback callback that gets triggered when the ambient light mode changes. + */ + fun start(callback: Callback) { + if (DEBUG) Log.d(TAG, "start monitoring ambient light mode") + + if (lightSensor.isEmpty) { + if (DEBUG) Log.w(TAG, "light sensor not available") + return + } + + if (algorithm.isEmpty) { + if (DEBUG) Log.w(TAG, "debounce algorithm not available") + return + } + + algorithm.get().start(callback) + sensorManager.registerListener( + mSensorEventListener, + lightSensor.get(), + SensorManager.SENSOR_DELAY_NORMAL, + ) + } + + /** Stop monitoring the current ambient light mode. */ + fun stop() { + if (DEBUG) Log.d(TAG, "stop monitoring ambient light mode") + + if (algorithm.isPresent) { + algorithm.get().stop() + } + sensorManager.unregisterListener(mSensorEventListener) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println() + pw.println("Ambient light mode monitor:") + pw.println(" lightSensor=$lightSensor") + pw.println() + } + + private val mSensorEventListener: SensorEventListener = + object : SensorEventListener { + override fun onSensorChanged(event: SensorEvent) { + if (event.values.isEmpty()) { + if (DEBUG) Log.w(TAG, "SensorEvent doesn't have any value") + return + } + + if (algorithm.isPresent) { + algorithm.get().onUpdateLightSensorEvent(event.values[0]) + } + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { + // Do nothing. + } + } + + /** Interface of the ambient light mode callback, which gets triggered when the mode changes. */ + interface Callback { + fun onChange(@AmbientLightMode mode: Int) + } + + /** Interface of the algorithm that transforms light sensor events to an ambient light mode. */ + interface DebounceAlgorithm { + // Setting Callback to nullable so mockito can verify without throwing NullPointerException. + fun start(callback: Callback?) + + fun stop() + + fun onUpdateLightSensorEvent(value: Float) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ChargingStatusProvider.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ChargingStatusProvider.java new file mode 100644 index 000000000000..8cc399b0a22b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ChargingStatusProvider.java @@ -0,0 +1,258 @@ +/* + * 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.lowlightclock; + +import android.content.Context; +import android.content.res.Resources; +import android.os.BatteryManager; +import android.os.RemoteException; +import android.text.format.Formatter; +import android.util.Log; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.util.Preconditions; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.fuelgauge.BatteryStatus; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.res.R; + +import java.text.NumberFormat; + +import javax.inject.Inject; + +/** + * Provides charging status as a string to a registered callback such that it can be displayed to + * the user (e.g. on the low-light clock). + * TODO(b/223681352): Make this code shareable with {@link KeyguardIndicationController}. + */ +public class ChargingStatusProvider { + private static final String TAG = "ChargingStatusProvider"; + + private final Resources mResources; + private final Context mContext; + private final IBatteryStats mBatteryInfo; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final BatteryState mBatteryState = new BatteryState(); + // This callback is registered with KeyguardUpdateMonitor, which only keeps weak references to + // its callbacks. Therefore, an explicit reference needs to be kept here to avoid the + // callback being GC'd. + private ChargingStatusCallback mChargingStatusCallback; + + private Callback mCallback; + + @Inject + public ChargingStatusProvider( + Context context, + @Main Resources resources, + IBatteryStats iBatteryStats, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + mContext = context; + mResources = resources; + mBatteryInfo = iBatteryStats; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + } + + /** + * Start using the {@link ChargingStatusProvider}. + * @param callback A callback to be called when the charging status changes. + */ + public void startUsing(Callback callback) { + Preconditions.checkState( + mCallback == null, "ChargingStatusProvider already started!"); + mCallback = callback; + mChargingStatusCallback = new ChargingStatusCallback(); + mKeyguardUpdateMonitor.registerCallback(mChargingStatusCallback); + reportStatusToCallback(); + } + + /** + * Stop using the {@link ChargingStatusProvider}. + */ + public void stopUsing() { + mCallback = null; + + if (mChargingStatusCallback != null) { + mKeyguardUpdateMonitor.removeCallback(mChargingStatusCallback); + mChargingStatusCallback = null; + } + } + + private String computeChargingString() { + if (!mBatteryState.isValid()) { + return null; + } + + int chargingId; + + if (mBatteryState.isBatteryDefender()) { + return mResources.getString( + R.string.keyguard_plugged_in_charging_limited, + mBatteryState.getBatteryLevelAsPercentage()); + } else if (mBatteryState.isPowerCharged()) { + return mResources.getString(R.string.keyguard_charged); + } + + final long chargingTimeRemaining = mBatteryState.getChargingTimeRemaining(mBatteryInfo); + final boolean hasChargingTime = chargingTimeRemaining > 0; + if (mBatteryState.isPowerPluggedInWired()) { + switch (mBatteryState.getChargingSpeed(mContext)) { + case BatteryStatus.CHARGING_FAST: + chargingId = hasChargingTime + ? R.string.keyguard_indication_charging_time_fast + : R.string.keyguard_plugged_in_charging_fast; + break; + case BatteryStatus.CHARGING_SLOWLY: + chargingId = hasChargingTime + ? R.string.keyguard_indication_charging_time_slowly + : R.string.keyguard_plugged_in_charging_slowly; + break; + default: + chargingId = hasChargingTime + ? R.string.keyguard_indication_charging_time + : R.string.keyguard_plugged_in; + break; + } + } else if (mBatteryState.isPowerPluggedInWireless()) { + chargingId = hasChargingTime + ? R.string.keyguard_indication_charging_time_wireless + : R.string.keyguard_plugged_in_wireless; + } else if (mBatteryState.isPowerPluggedInDocked()) { + chargingId = hasChargingTime + ? R.string.keyguard_indication_charging_time_dock + : R.string.keyguard_plugged_in_dock; + } else { + chargingId = hasChargingTime + ? R.string.keyguard_indication_charging_time + : R.string.keyguard_plugged_in; + } + + final String percentage = mBatteryState.getBatteryLevelAsPercentage(); + if (hasChargingTime) { + final String chargingTimeFormatted = + Formatter.formatShortElapsedTimeRoundingUpToMinutes( + mContext, chargingTimeRemaining); + return mResources.getString(chargingId, chargingTimeFormatted, + percentage); + } else { + return mResources.getString(chargingId, percentage); + } + } + + private void reportStatusToCallback() { + if (mCallback != null) { + final boolean shouldShowStatus = + mBatteryState.isPowerPluggedIn() || mBatteryState.isBatteryDefenderEnabled(); + mCallback.onChargingStatusChanged(shouldShowStatus, computeChargingString()); + } + } + + private class ChargingStatusCallback extends KeyguardUpdateMonitorCallback { + @Override + public void onRefreshBatteryInfo(BatteryStatus status) { + mBatteryState.setBatteryStatus(status); + reportStatusToCallback(); + } + } + + /*** + * A callback to be called when the charging status changes. + */ + public interface Callback { + /*** + * Called when the charging status changes. + * @param shouldShowStatus Whether or not to show a charging status message. + * @param statusMessage A charging status message. + */ + void onChargingStatusChanged(boolean shouldShowStatus, String statusMessage); + } + + /*** + * A wrapper around {@link BatteryStatus} for fetching various properties of the current + * battery and charging state. + */ + private static class BatteryState { + private BatteryStatus mBatteryStatus; + + public void setBatteryStatus(BatteryStatus batteryStatus) { + mBatteryStatus = batteryStatus; + } + + public boolean isValid() { + return mBatteryStatus != null; + } + + public long getChargingTimeRemaining(IBatteryStats batteryInfo) { + try { + return isPowerPluggedIn() ? batteryInfo.computeChargeTimeRemaining() : -1; + } catch (RemoteException e) { + Log.e(TAG, "Error calling IBatteryStats: ", e); + return -1; + } + } + + public boolean isBatteryDefenderEnabled() { + return isValid() && mBatteryStatus.isPluggedIn() && isBatteryDefender(); + } + + public boolean isBatteryDefender() { + return isValid() && mBatteryStatus.isBatteryDefender(); + } + + public int getBatteryLevel() { + return isValid() ? mBatteryStatus.level : 0; + } + + public int getChargingSpeed(Context context) { + return isValid() ? mBatteryStatus.getChargingSpeed(context) : 0; + } + + public boolean isPowerCharged() { + return isValid() && mBatteryStatus.isCharged(); + } + + public boolean isPowerPluggedIn() { + return isValid() && mBatteryStatus.isPluggedIn() && isChargingOrFull(); + } + + public boolean isPowerPluggedInWired() { + return isValid() + && mBatteryStatus.isPluggedInWired() + && isChargingOrFull(); + } + + public boolean isPowerPluggedInWireless() { + return isValid() + && mBatteryStatus.isPluggedInWireless() + && isChargingOrFull(); + } + + public boolean isPowerPluggedInDocked() { + return isValid() && mBatteryStatus.isPluggedInDock() && isChargingOrFull(); + } + + private boolean isChargingOrFull() { + return isValid() + && (mBatteryStatus.status == BatteryManager.BATTERY_STATUS_CHARGING + || mBatteryStatus.isCharged()); + } + + private String getBatteryLevelAsPercentage() { + return NumberFormat.getPercentInstance().format(getBatteryLevel() / 100f); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt new file mode 100644 index 000000000000..4c1da0198498 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.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.lowlightclock + +import android.content.Intent +import android.content.IntentFilter +import android.os.UserManager +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.shared.condition.Condition +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.cancellable +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class DirectBootCondition +@Inject +constructor( + broadcastDispatcher: BroadcastDispatcher, + private val userManager: UserManager, + @Application private val coroutineScope: CoroutineScope, +) : Condition(coroutineScope) { + private var job: Job? = null + private val directBootFlow = + broadcastDispatcher + .broadcastFlow(IntentFilter(Intent.ACTION_USER_UNLOCKED)) + .map { !userManager.isUserUnlocked } + .cancellable() + .distinctUntilChanged() + + override fun start() { + job = coroutineScope.launch { directBootFlow.collect { updateCondition(it) } } + updateCondition(!userManager.isUserUnlocked) + } + + override fun stop() { + job?.cancel() + } + + override fun getStartStrategy(): Int { + return START_EAGERLY + } +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java new file mode 100644 index 000000000000..7f21d0707f63 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java @@ -0,0 +1,137 @@ +/* + * 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.lowlightclock; + +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.systemui.dagger.qualifiers.Application; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.statusbar.commandline.Command; +import com.android.systemui.statusbar.commandline.CommandRegistry; + +import kotlinx.coroutines.CoroutineScope; + +import java.io.PrintWriter; +import java.util.List; + +import javax.inject.Inject; + +/** + * This condition registers for and fulfills cmd shell commands to force a device into or out of + * low-light conditions. + */ +public class ForceLowLightCondition extends Condition { + /** + * Command root + */ + public static final String COMMAND_ROOT = "low-light"; + /** + * Command for forcing device into low light. + */ + public static final String COMMAND_ENABLE_LOW_LIGHT = "enable"; + + /** + * Command for preventing a device from entering low light. + */ + public static final String COMMAND_DISABLE_LOW_LIGHT = "disable"; + + /** + * Command for clearing previously forced low-light conditions. + */ + public static final String COMMAND_CLEAR_LOW_LIGHT = "clear"; + + private static final String TAG = "ForceLowLightCondition"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + /** + * Default Constructor. + * + * @param commandRegistry command registry to register commands with. + */ + @Inject + public ForceLowLightCondition( + @Application CoroutineScope scope, + CommandRegistry commandRegistry + ) { + super(scope, null, true); + + if (DEBUG) { + Log.d(TAG, "registering commands"); + } + commandRegistry.registerCommand(COMMAND_ROOT, () -> new Command() { + @Override + public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) { + if (args.size() != 1) { + pw.println("no command specified"); + help(pw); + return; + } + + final String cmd = args.get(0); + + if (TextUtils.equals(cmd, COMMAND_ENABLE_LOW_LIGHT)) { + logAndPrint(pw, "forcing low light"); + updateCondition(true); + } else if (TextUtils.equals(cmd, COMMAND_DISABLE_LOW_LIGHT)) { + logAndPrint(pw, "forcing to not enter low light"); + updateCondition(false); + } else if (TextUtils.equals(cmd, COMMAND_CLEAR_LOW_LIGHT)) { + logAndPrint(pw, "clearing any forced low light"); + clearCondition(); + } else { + pw.println("invalid command"); + help(pw); + } + } + + @Override + public void help(@NonNull PrintWriter pw) { + pw.println("Usage: adb shell cmd statusbar low-light <cmd>"); + pw.println("Supported commands:"); + pw.println(" - enable"); + pw.println(" forces device into low-light"); + pw.println(" - disable"); + pw.println(" forces device to not enter low-light"); + pw.println(" - clear"); + pw.println(" clears any previously forced state"); + } + + private void logAndPrint(PrintWriter pw, String message) { + pw.println(message); + if (DEBUG) { + Log.d(TAG, message); + } + } + }); + } + + @Override + protected void start() { + } + + @Override + protected void stop() { + } + + @Override + protected int getStartStrategy() { + return START_EAGERLY; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockAnimationProvider.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockAnimationProvider.java new file mode 100644 index 000000000000..6de599803a57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockAnimationProvider.java @@ -0,0 +1,127 @@ +/* + * 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.lowlightclock; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.view.View; +import android.view.animation.Interpolator; + +import com.android.app.animation.Interpolators; +import com.android.dream.lowlight.util.TruncatedInterpolator; +import com.android.systemui.lowlightclock.dagger.LowLightModule; +import com.android.systemui.statusbar.CrossFadeHelper; + +import javax.inject.Inject; +import javax.inject.Named; + +/*** + * A class that provides the animations used by the low-light clock. + * + * The entry and exit animations are opposites, with the only difference being a delay before the + * text fades in on entry. + */ +public class LowLightClockAnimationProvider { + private final int mYTranslationAnimationInStartOffset; + private final long mYTranslationAnimationInDurationMillis; + private final long mAlphaAnimationInStartDelayMillis; + private final long mAlphaAnimationDurationMillis; + + /** + * Custom interpolator used for the translate out animation, which uses an emphasized easing + * like the translate in animation, but is scaled to match the length of the alpha animation. + */ + private final Interpolator mTranslationOutInterpolator; + + @Inject + public LowLightClockAnimationProvider( + @Named(LowLightModule.Y_TRANSLATION_ANIMATION_OFFSET) + int yTranslationAnimationInStartOffset, + @Named(LowLightModule.Y_TRANSLATION_ANIMATION_DURATION_MILLIS) + long yTranslationAnimationInDurationMillis, + @Named(LowLightModule.ALPHA_ANIMATION_IN_START_DELAY_MILLIS) + long alphaAnimationInStartDelayMillis, + @Named(LowLightModule.ALPHA_ANIMATION_DURATION_MILLIS) + long alphaAnimationDurationMillis) { + mYTranslationAnimationInStartOffset = yTranslationAnimationInStartOffset; + mYTranslationAnimationInDurationMillis = yTranslationAnimationInDurationMillis; + mAlphaAnimationInStartDelayMillis = alphaAnimationInStartDelayMillis; + mAlphaAnimationDurationMillis = alphaAnimationDurationMillis; + + mTranslationOutInterpolator = new TruncatedInterpolator(Interpolators.EMPHASIZED, + /*originalDuration=*/ mYTranslationAnimationInDurationMillis, + /*newDuration=*/ mAlphaAnimationDurationMillis); + } + + /*** + * Provides an animation for when the given views become visible. + * @param views Any number of views to animate in together. + */ + public Animator provideAnimationIn(View... views) { + final AnimatorSet animatorSet = new AnimatorSet(); + + for (View view : views) { + if (view == null) continue; + // Set the alpha to 0 to start because the alpha animation has a start delay. + CrossFadeHelper.fadeOut(view, 0f, false); + + final Animator alphaAnimator = + ObjectAnimator.ofFloat(view, View.ALPHA, 1f); + alphaAnimator.setStartDelay(mAlphaAnimationInStartDelayMillis); + alphaAnimator.setDuration(mAlphaAnimationDurationMillis); + alphaAnimator.setInterpolator(Interpolators.LINEAR); + + final Animator positionAnimator = ObjectAnimator + .ofFloat(view, View.TRANSLATION_Y, mYTranslationAnimationInStartOffset, 0f); + positionAnimator.setDuration(mYTranslationAnimationInDurationMillis); + positionAnimator.setInterpolator(Interpolators.EMPHASIZED); + + // The position animator must be started first since the alpha animator has a start + // delay. + animatorSet.playTogether(positionAnimator, alphaAnimator); + } + + return animatorSet; + } + + /*** + * Provides an animation for when the given views are going out of view. + * @param views Any number of views to animate out. + */ + public Animator provideAnimationOut(View... views) { + final AnimatorSet animatorSet = new AnimatorSet(); + + for (View view : views) { + if (view == null) continue; + final Animator alphaAnimator = + ObjectAnimator.ofFloat(view, View.ALPHA, 0f); + alphaAnimator.setDuration(mAlphaAnimationDurationMillis); + alphaAnimator.setInterpolator(Interpolators.LINEAR); + + final Animator positionAnimator = ObjectAnimator + .ofFloat(view, View.TRANSLATION_Y, mYTranslationAnimationInStartOffset); + // Use the same duration as the alpha animation plus our custom interpolator. + positionAnimator.setDuration(mAlphaAnimationDurationMillis); + positionAnimator.setInterpolator(mTranslationOutInterpolator); + animatorSet.playTogether(alphaAnimator, positionAnimator); + } + + return animatorSet; + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java new file mode 100644 index 000000000000..e91be5028777 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java @@ -0,0 +1,77 @@ +/* + * 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.lowlightclock; + +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.dagger.qualifiers.Application; +import com.android.systemui.shared.condition.Condition; + +import kotlinx.coroutines.CoroutineScope; + +import javax.inject.Inject; + +/** + * Condition for monitoring when the device enters and exits lowlight mode. + */ +public class LowLightCondition extends Condition { + private final AmbientLightModeMonitor mAmbientLightModeMonitor; + private final UiEventLogger mUiEventLogger; + + @Inject + public LowLightCondition(@Application CoroutineScope scope, + AmbientLightModeMonitor ambientLightModeMonitor, + UiEventLogger uiEventLogger) { + super(scope); + mAmbientLightModeMonitor = ambientLightModeMonitor; + mUiEventLogger = uiEventLogger; + } + + @Override + protected void start() { + mAmbientLightModeMonitor.start(this::onLowLightChanged); + } + + @Override + protected void stop() { + mAmbientLightModeMonitor.stop(); + + // Reset condition met to false. + updateCondition(false); + } + + @Override + protected int getStartStrategy() { + // As this condition keeps the lowlight sensor active, it should only run when needed. + return START_WHEN_NEEDED; + } + + private void onLowLightChanged(int lowLightMode) { + if (lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED) { + // Ignore undecided mode changes. + return; + } + + final boolean isLowLight = lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK; + if (isLowLight == isConditionMet()) { + // No change in condition, don't do anything. + return; + } + mUiEventLogger.log(isLowLight ? LowLightDockEvent.AMBIENT_LIGHT_TO_DARK + : LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT); + updateCondition(isLowLight); + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDisplayController.kt index 9a7f99f3247b..9a9d813b18c5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDisplayController.kt @@ -14,16 +14,10 @@ * limitations under the License. */ -package com.android.systemui.scene.ui.composable.transitions +package com.android.systemui.lowlightclock -import androidx.compose.animation.core.tween -import com.android.compose.animation.scene.TransitionBuilder -import kotlin.time.Duration.Companion.milliseconds +interface LowLightDisplayController { + fun isDisplayBrightnessModeSupported(): Boolean -fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition( - durationScale: Double = 1.0 -) { - spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) + fun setDisplayBrightnessModeEnabled(enabled: Boolean) } - -private val DefaultDuration = 300.milliseconds diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDockEvent.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDockEvent.kt new file mode 100644 index 000000000000..b99aeb6eeacc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDockEvent.kt @@ -0,0 +1,31 @@ +/* + * 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.lowlightclock + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class LowLightDockEvent(private val id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Ambient light changed from light to dark") AMBIENT_LIGHT_TO_DARK(999), + @UiEvent(doc = "The low light mode has started") LOW_LIGHT_STARTED(1000), + @UiEvent(doc = "Ambient light changed from dark to light") AMBIENT_LIGHT_TO_LIGHT(1001), + @UiEvent(doc = "The low light mode has stopped") LOW_LIGHT_STOPPED(1002); + + override fun getId(): Int { + return id + } +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightLogger.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightLogger.kt new file mode 100644 index 000000000000..11d75215edf5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightLogger.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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.lowlightclock + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.lowlightclock.dagger.LowLightLog +import javax.inject.Inject + +/** Logs to a {@link LogBuffer} anything related to low-light features. */ +class LowLightLogger @Inject constructor(@LowLightLog private val buffer: LogBuffer) { + /** Logs a debug message to the buffer. */ + fun d(tag: String, message: String) = buffer.log(tag, LogLevel.DEBUG, message) +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java new file mode 100644 index 000000000000..912ace7675d5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java @@ -0,0 +1,133 @@ +/* + * 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.lowlightclock; + +import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT; +import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR; +import static com.android.systemui.dreams.dagger.DreamModule.LOW_LIGHT_DREAM_SERVICE; +import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; +import static com.android.systemui.lowlightclock.dagger.LowLightModule.LOW_LIGHT_PRECONDITIONS; + +import android.content.ComponentName; +import android.content.pm.PackageManager; + +import androidx.annotation.Nullable; + +import com.android.dream.lowlight.LowLightDreamManager; +import com.android.systemui.dagger.qualifiers.SystemUser; +import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.condition.ConditionalCoreStartable; + +import dagger.Lazy; + +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Tracks environment (low-light or not) in order to correctly show or hide a low-light clock while + * dreaming. + */ +public class LowLightMonitor extends ConditionalCoreStartable implements Monitor.Callback, + ScreenLifecycle.Observer { + private static final String TAG = "LowLightMonitor"; + + private final Lazy<LowLightDreamManager> mLowLightDreamManager; + private final Monitor mConditionsMonitor; + private final Lazy<Set<Condition>> mLowLightConditions; + private Monitor.Subscription.Token mSubscriptionToken; + private ScreenLifecycle mScreenLifecycle; + private final LowLightLogger mLogger; + + private final ComponentName mLowLightDreamService; + + private final PackageManager mPackageManager; + + @Inject + public LowLightMonitor(Lazy<LowLightDreamManager> lowLightDreamManager, + @SystemUser Monitor conditionsMonitor, + @Named(LOW_LIGHT_PRECONDITIONS) Lazy<Set<Condition>> lowLightConditions, + ScreenLifecycle screenLifecycle, + LowLightLogger lowLightLogger, + @Nullable @Named(LOW_LIGHT_DREAM_SERVICE) ComponentName lowLightDreamService, + PackageManager packageManager) { + super(conditionsMonitor); + mLowLightDreamManager = lowLightDreamManager; + mConditionsMonitor = conditionsMonitor; + mLowLightConditions = lowLightConditions; + mScreenLifecycle = screenLifecycle; + mLogger = lowLightLogger; + mLowLightDreamService = lowLightDreamService; + mPackageManager = packageManager; + } + + @Override + public void onConditionsChanged(boolean allConditionsMet) { + mLogger.d(TAG, "Low light enabled: " + allConditionsMet); + + mLowLightDreamManager.get().setAmbientLightMode(allConditionsMet + ? AMBIENT_LIGHT_MODE_LOW_LIGHT : AMBIENT_LIGHT_MODE_REGULAR); + } + + @Override + public void onScreenTurnedOn() { + if (mSubscriptionToken == null) { + mLogger.d(TAG, "Screen turned on. Subscribing to low light conditions."); + + mSubscriptionToken = mConditionsMonitor.addSubscription( + new Monitor.Subscription.Builder(this) + .addConditions(mLowLightConditions.get()) + .build()); + } + } + + + @Override + public void onScreenTurnedOff() { + if (mSubscriptionToken != null) { + mLogger.d(TAG, "Screen turned off. Removing subscription to low light conditions."); + + mConditionsMonitor.removeSubscription(mSubscriptionToken); + mSubscriptionToken = null; + } + } + + @Override + protected void onStart() { + if (mLowLightDreamService != null) { + // Note that the dream service is disabled by default. This prevents the dream from + // appearing in settings on devices that don't have it explicitly excluded (done in + // the settings overlay). Therefore, the component is enabled if it is to be used + // here. + mPackageManager.setComponentEnabledSetting( + mLowLightDreamService, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP + ); + } else { + // If there is no low light dream service, do not observe conditions. + return; + } + + mScreenLifecycle.addObserver(this); + if (mScreenLifecycle.getScreenState() == SCREEN_ON) { + onScreenTurnedOn(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java new file mode 100644 index 000000000000..fd6ce1762a28 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java @@ -0,0 +1,87 @@ +/* + * 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.lowlightclock; + +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.systemui.dagger.qualifiers.Application; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.util.settings.SecureSettings; + +import kotlinx.coroutines.CoroutineScope; + +import javax.inject.Inject; + +/** + * Condition for monitoring if the screensaver setting is enabled. + */ +public class ScreenSaverEnabledCondition extends Condition { + private static final String TAG = ScreenSaverEnabledCondition.class.getSimpleName(); + + private final boolean mScreenSaverEnabledByDefaultConfig; + private final SecureSettings mSecureSettings; + + private final ContentObserver mScreenSaverSettingObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + updateScreenSaverEnabledSetting(); + } + }; + + @Inject + public ScreenSaverEnabledCondition(@Application CoroutineScope scope, @Main Resources resources, + SecureSettings secureSettings) { + super(scope); + mScreenSaverEnabledByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsEnabledByDefault); + mSecureSettings = secureSettings; + } + + @Override + protected void start() { + mSecureSettings.registerContentObserverForUserSync( + Settings.Secure.SCREENSAVER_ENABLED, + mScreenSaverSettingObserver, UserHandle.USER_CURRENT); + updateScreenSaverEnabledSetting(); + } + + @Override + protected void stop() { + mSecureSettings.unregisterContentObserverSync(mScreenSaverSettingObserver); + } + + @Override + protected int getStartStrategy() { + return START_EAGERLY; + } + + private void updateScreenSaverEnabledSetting() { + final boolean enabled = mSecureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + mScreenSaverEnabledByDefaultConfig ? 1 : 0, + UserHandle.USER_CURRENT) != 0; + if (!enabled) { + Log.i(TAG, "Disabling low-light clock because screen saver has been disabled"); + } + updateCondition(enabled); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightLog.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightLog.kt new file mode 100644 index 000000000000..0819664c921c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightLog.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 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.lowlightclock.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for logging related to low light features. */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class LowLightLog diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java new file mode 100644 index 000000000000..c08be51c0699 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java @@ -0,0 +1,150 @@ +/* + * 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.lowlightclock.dagger; + +import android.content.res.Resources; +import android.hardware.Sensor; + +import com.android.dream.lowlight.dagger.LowLightDreamModule; +import com.android.systemui.CoreStartable; +import com.android.systemui.communal.DeviceInactiveCondition; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.LogBufferFactory; +import com.android.systemui.lowlightclock.AmbientLightModeMonitor; +import com.android.systemui.lowlightclock.DirectBootCondition; +import com.android.systemui.lowlightclock.ForceLowLightCondition; +import com.android.systemui.lowlightclock.LowLightCondition; +import com.android.systemui.lowlightclock.LowLightDisplayController; +import com.android.systemui.lowlightclock.LowLightMonitor; +import com.android.systemui.lowlightclock.ScreenSaverEnabledCondition; +import com.android.systemui.res.R; +import com.android.systemui.shared.condition.Condition; + +import dagger.Binds; +import dagger.BindsOptionalOf; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; +import dagger.multibindings.IntoSet; + +import javax.inject.Named; + +@Module(includes = LowLightDreamModule.class) +public abstract class LowLightModule { + public static final String Y_TRANSLATION_ANIMATION_OFFSET = + "y_translation_animation_offset"; + public static final String Y_TRANSLATION_ANIMATION_DURATION_MILLIS = + "y_translation_animation_duration_millis"; + public static final String ALPHA_ANIMATION_IN_START_DELAY_MILLIS = + "alpha_animation_in_start_delay_millis"; + public static final String ALPHA_ANIMATION_DURATION_MILLIS = + "alpha_animation_duration_millis"; + public static final String LOW_LIGHT_PRECONDITIONS = "low_light_preconditions"; + public static final String LIGHT_SENSOR = "low_light_monitor_light_sensor"; + + + /** + * Provides a {@link LogBuffer} for logs related to low-light features. + */ + @Provides + @SysUISingleton + @LowLightLog + public static LogBuffer provideLowLightLogBuffer(LogBufferFactory factory) { + return factory.create("LowLightLog", 250); + } + + @Binds + @IntoSet + @Named(LOW_LIGHT_PRECONDITIONS) + abstract Condition bindScreenSaverEnabledCondition(ScreenSaverEnabledCondition condition); + + @Provides + @IntoSet + @Named(com.android.systemui.lowlightclock.dagger.LowLightModule.LOW_LIGHT_PRECONDITIONS) + static Condition provideLowLightCondition(LowLightCondition lowLightCondition, + DirectBootCondition directBootCondition) { + // Start lowlight if we are either in lowlight or in direct boot. The ordering of the + // conditions matters here since we don't want to start the lowlight condition if + // we are in direct boot mode. + return directBootCondition.or(lowLightCondition); + } + + @Binds + @IntoSet + @Named(LOW_LIGHT_PRECONDITIONS) + abstract Condition bindForceLowLightCondition(ForceLowLightCondition condition); + + @Binds + @IntoSet + @Named(LOW_LIGHT_PRECONDITIONS) + abstract Condition bindDeviceInactiveCondition(DeviceInactiveCondition condition); + + @BindsOptionalOf + abstract LowLightDisplayController bindsLowLightDisplayController(); + + @BindsOptionalOf + @Named(LIGHT_SENSOR) + abstract Sensor bindsLightSensor(); + + @BindsOptionalOf + abstract AmbientLightModeMonitor.DebounceAlgorithm bindsDebounceAlgorithm(); + + /** + * + */ + @Provides + @Named(Y_TRANSLATION_ANIMATION_OFFSET) + static int providesAnimationInOffset(@Main Resources resources) { + return resources.getDimensionPixelOffset( + R.dimen.low_light_clock_translate_animation_offset); + } + + /** + * + */ + @Provides + @Named(Y_TRANSLATION_ANIMATION_DURATION_MILLIS) + static long providesAnimationDurationMillis(@Main Resources resources) { + return resources.getInteger(R.integer.low_light_clock_translate_animation_duration_ms); + } + + /** + * + */ + @Provides + @Named(ALPHA_ANIMATION_IN_START_DELAY_MILLIS) + static long providesAlphaAnimationInStartDelayMillis(@Main Resources resources) { + return resources.getInteger(R.integer.low_light_clock_alpha_animation_in_start_delay_ms); + } + + /** + * + */ + @Provides + @Named(ALPHA_ANIMATION_DURATION_MILLIS) + static long providesAlphaAnimationDurationMillis(@Main Resources resources) { + return resources.getInteger(R.integer.low_light_clock_alpha_animation_duration_ms); + } + /** Inject into LowLightMonitor. */ + @Binds + @IntoMap + @ClassKey(LowLightMonitor.class) + abstract CoreStartable bindLowLightMonitor(LowLightMonitor lowLightMonitor); +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt index f1f299aac2b4..52749c54b9ba 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt @@ -1268,13 +1268,21 @@ class LegacyMediaDataManagerImpl( } private fun getResumeMediaAction(action: Runnable): MediaAction { + val iconId = + if (Flags.mediaControlsUiUpdate()) { + R.drawable.ic_media_play_button + } else { + R.drawable.ic_media_play + } return MediaAction( - Icon.createWithResource(context, R.drawable.ic_media_play) - .setTint(themeText) - .loadDrawable(context), + Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context), action, context.getString(R.string.controls_media_resume), - context.getDrawable(R.drawable.ic_media_play_container), + if (Flags.mediaControlsUiUpdate()) { + context.getDrawable(R.drawable.ic_media_play_button_container) + } else { + context.getDrawable(R.drawable.ic_media_play_container) + }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt index a176e0c1c2a6..8bb7303a8386 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt @@ -43,7 +43,9 @@ import android.support.v4.media.MediaMetadataCompat import android.text.TextUtils import android.util.Log import androidx.media.utils.MediaConstants +import com.android.app.tracing.coroutines.asyncTraced as async import com.android.app.tracing.coroutines.traceCoroutine +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -67,7 +69,6 @@ import kotlin.coroutines.coroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import com.android.app.tracing.coroutines.asyncTraced as async import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive @@ -511,13 +512,21 @@ constructor( sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) private fun getResumeMediaAction(action: Runnable): MediaAction { + val iconId = + if (Flags.mediaControlsUiUpdate()) { + R.drawable.ic_media_play_button + } else { + R.drawable.ic_media_play + } return MediaAction( - Icon.createWithResource(context, R.drawable.ic_media_play) - .setTint(themeText) - .loadDrawable(context), + Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context), action, context.getString(R.string.controls_media_resume), - context.getDrawable(R.drawable.ic_media_play_container), + if (Flags.mediaControlsUiUpdate()) { + context.getDrawable(R.drawable.ic_media_play_button_container) + } else { + context.getDrawable(R.drawable.ic_media_play_container) + }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index a524db4437a5..587a678c6ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -1197,13 +1197,21 @@ class MediaDataProcessor( } private fun getResumeMediaAction(action: Runnable): MediaAction { + val iconId = + if (Flags.mediaControlsUiUpdate()) { + R.drawable.ic_media_play_button + } else { + R.drawable.ic_media_play + } return MediaAction( - Icon.createWithResource(context, R.drawable.ic_media_play) - .setTint(themeText) - .loadDrawable(context), + Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context), action, context.getString(R.string.controls_media_resume), - context.getDrawable(R.drawable.ic_media_play_container), + if (Flags.mediaControlsUiUpdate()) { + context.getDrawable(R.drawable.ic_media_play_button_container) + } else { + context.getDrawable(R.drawable.ic_media_play_container) + }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 0ada931aea43..a9732b8e7d0c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -28,7 +28,6 @@ import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityNodeInfo; import android.widget.CheckBox; import android.widget.TextView; @@ -159,12 +158,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { && !mController.hasAdjustVolumeUserRestriction()) { setUpDeviceIcon(device); updateProgressBarColor(); - setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/, + setSingleLineLayout(device.getName(), false /* showSeekBar*/, true /* showProgressBar */, false /* showCheckBox */, false /* showEndTouchArea */); } else { setUpDeviceIcon(device); - setSingleLineLayout(getItemTitle(device)); + setSingleLineLayout(device.getName()); } } else { // Set different layout for each device @@ -173,7 +172,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateUnmutedVolumeIcon(device); mCurrentActivePosition = position; updateFullItemClickListener(v -> onItemClick(v, device)); - setSingleLineLayout(getItemTitle(device)); + setSingleLineLayout(device.getName()); initFakeActiveDevice(device); } else if (device.hasSubtext()) { boolean isActiveWithOngoingSession = @@ -189,10 +188,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateEndClickAreaAsSessionEditing(device, isHost ? R.drawable.media_output_status_edit_session : R.drawable.ic_sound_bars_anim); - setTwoLineLayout(device, null /* title */, true /* bFocused */, + setTwoLineLayout(device.getName() /* title */, true /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, false /* showStatus */, - true /* showEndTouchArea */, false /* isFakeActive */); + true /* showEndTouchArea */); initSeekbar(device, isCurrentSeekbarInvisible); } else { if (currentlyConnected) { @@ -214,24 +213,23 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateTwoLineLayoutContentAlpha( updateClickActionBasedOnSelectionBehavior(device) ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); - setTwoLineLayout(device, currentlyConnected /* bFocused */, + setTwoLineLayout(device.getName(), currentlyConnected /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, - deviceStatusIcon != null /* showStatus */, - false /* isFakeActive */); + deviceStatusIcon != null /* showStatus */); } } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); updateConnectionFailedStatusIcon(); mSubTitleText.setText(R.string.media_output_dialog_connect_failed); updateFullItemClickListener(v -> onItemClick(v, device)); - setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */, + setTwoLineLayout(device.getName(), false /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, - true /* showStatus */, false /*isFakeActive*/); + true /* showStatus */); } else if (device.getState() == MediaDeviceState.STATE_GROUPING) { setUpDeviceIcon(device); updateProgressBarColor(); - setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/, + setSingleLineLayout(device.getName(), false /* showSeekBar*/, true /* showProgressBar */, false /* showCheckBox */, false /* showEndTouchArea */); } else if (mController.getSelectedMediaDevice().size() > 1 @@ -244,7 +242,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateEndClickArea(device, isDeviceDeselectable); disableFocusPropertyForView(mContainerLayout); setUpContentDescriptionForView(mSeekBar, device); - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + setSingleLineLayout(device.getName(), true /* showSeekBar */, false /* showProgressBar */, true /* showCheckBox */, true /* showEndTouchArea */); initSeekbar(device, isCurrentSeekbarInvisible); @@ -256,7 +254,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { // mark as disconnected and set special click listener setUpDeviceIcon(device); updateFullItemClickListener(v -> cancelMuteAwaitConnection()); - setSingleLineLayout(getItemTitle(device)); + setSingleLineLayout(device.getName()); } else if (device.hasOngoingSession()) { mCurrentActivePosition = position; updateUnmutedVolumeIcon(device); @@ -264,7 +262,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { ? R.drawable.media_output_status_edit_session : R.drawable.ic_sound_bars_anim); mEndClickIcon.setVisibility(View.VISIBLE); - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + setSingleLineLayout(device.getName(), true /* showSeekBar */, false /* showProgressBar */, false /* showCheckBox */, true /* showEndTouchArea */); initSeekbar(device, isCurrentSeekbarInvisible); @@ -279,7 +277,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateEndClickArea(device, isDeviceDeselectable); disableFocusPropertyForView(mContainerLayout); setUpContentDescriptionForView(mSeekBar, device); - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + setSingleLineLayout(device.getName(), true /* showSeekBar */, false /* showProgressBar */, true /* showCheckBox */, true /* showEndTouchArea */); initSeekbar(device, isCurrentSeekbarInvisible); @@ -288,7 +286,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { disableFocusPropertyForView(mContainerLayout); setUpContentDescriptionForView(mSeekBar, device); mCurrentActivePosition = position; - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + setSingleLineLayout(device.getName(), true /* showSeekBar */, false /* showProgressBar */, false /* showCheckBox */, false /* showEndTouchArea */); initSeekbar(device, isCurrentSeekbarInvisible); @@ -299,12 +297,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateGroupableCheckBox(false, true, device); updateEndClickArea(device, true); updateFullItemClickListener(v -> onItemClick(v, device)); - setSingleLineLayout(getItemTitle(device), false /* showSeekBar */, + setSingleLineLayout(device.getName(), false /* showSeekBar */, false /* showProgressBar */, true /* showCheckBox */, true /* showEndTouchArea */); } else { setUpDeviceIcon(device); - setSingleLineLayout(getItemTitle(device)); + setSingleLineLayout(device.getName()); Drawable deviceStatusIcon = device.hasOngoingSession() ? mContext.getDrawable( R.drawable.ic_sound_bars_anim) @@ -427,8 +425,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mTitleIcon.setImageDrawable(addDrawable); mTitleIcon.setImageTintList( ColorStateList.valueOf(mController.getColorItemContent())); - mIconAreaLayout.setBackgroundTintList( - ColorStateList.valueOf(mController.getColorItemBackground())); mContainerLayout.setOnClickListener(mController::launchBluetoothPairing); } @@ -494,13 +490,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE ? R.string.accessibility_bluetooth_name : R.string.accessibility_cast_name, device.getName())); - view.setAccessibilityDelegate(new View.AccessibilityDelegate() { - public void onInitializeAccessibilityNodeInfo(View host, - AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - host.setOnClickListener(null); - } - }); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 1a2238cfbc9e..4958b223a2f2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -23,7 +23,6 @@ import android.animation.ValueAnimator; import android.app.WallpaperColors; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.Typeface; import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; @@ -95,10 +94,6 @@ public abstract class MediaOutputBaseAdapter extends mController.setCurrentColorScheme(wallpaperColors, isDarkTheme); } - CharSequence getItemTitle(MediaDevice device) { - return device.getName(); - } - boolean isCurrentlyConnected(MediaDevice device) { return TextUtils.equals(device.getId(), mController.getCurrentConnectedMediaDevice().getId()) @@ -188,6 +183,7 @@ public abstract class MediaOutputBaseAdapter extends mSubTitleText.setSelected(true); mTwoLineTitleText.setTextColor(mController.getColorItemContent()); mVolumeValueText.setTextColor(mController.getColorItemContent()); + mIconAreaLayout.setBackground(null); mSeekBar.setProgressTintList( ColorStateList.valueOf(mController.getColorSeekbarProgress())); } @@ -216,10 +212,6 @@ public abstract class MediaOutputBaseAdapter extends mItemLayout.setBackgroundTintList( ColorStateList.valueOf(isActive ? mController.getColorConnectedItemBackground() : mController.getColorItemBackground())); - mIconAreaLayout.setBackgroundTintList( - ColorStateList.valueOf(showSeekBar ? mController.getColorSeekbarProgress() - : showProgressBar ? mController.getColorConnectedItemBackground() - : mController.getColorItemBackground())); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSeekBar.setAlpha(1); mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); @@ -236,16 +228,14 @@ public abstract class MediaOutputBaseAdapter extends : mController.getItemMarginEndDefault(); } - void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, - boolean showProgressBar, boolean showSubtitle, boolean showStatus, - boolean isFakeActive) { - setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle, - showStatus, false, isFakeActive); + void setTwoLineLayout(CharSequence title, boolean showSeekBar, + boolean showProgressBar, boolean showSubtitle, boolean showStatus) { + setTwoLineLayout(title, showSeekBar, showProgressBar, showSubtitle, showStatus, false); } - void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused, + void setTwoLineLayout(CharSequence title, boolean showSeekBar, boolean showProgressBar, boolean showSubtitle, - boolean showStatus , boolean showEndTouchArea, boolean isFakeActive) { + boolean showStatus , boolean showEndTouchArea) { mTitleText.setVisibility(View.GONE); mTwoLineLayout.setVisibility(View.VISIBLE); mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE); @@ -253,17 +243,12 @@ public abstract class MediaOutputBaseAdapter extends mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); final Drawable backgroundDrawable; backgroundDrawable = mContext.getDrawable( - showSeekBar || isFakeActive ? R.drawable.media_output_item_background_active + showSeekBar ? R.drawable.media_output_item_background_active : R.drawable.media_output_item_background).mutate(); mItemLayout.setBackgroundTintList(ColorStateList.valueOf( - showSeekBar || isFakeActive ? mController.getColorConnectedItemBackground() + showSeekBar ? mController.getColorConnectedItemBackground() : mController.getColorItemBackground() )); - mIconAreaLayout.setBackgroundTintList( - ColorStateList.valueOf(showProgressBar || isFakeActive - ? mController.getColorConnectedItemBackground() - : showSeekBar ? mController.getColorSeekbarProgress() - : mController.getColorItemBackground())); if (showSeekBar) { updateSeekbarProgressBackground(); } @@ -277,12 +262,7 @@ public abstract class MediaOutputBaseAdapter extends mItemLayout.setBackground(backgroundDrawable); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); - mTwoLineTitleText.setTranslationY(0); - mTwoLineTitleText.setText(device == null ? title : getItemTitle(device)); - mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString( - bFocused ? com.android.internal.R.string.config_headlineFontFamilyMedium - : com.android.internal.R.string.config_headlineFontFamily), - Typeface.NORMAL)); + mTwoLineTitleText.setText(title); } void updateSeekbarProgressBackground() { @@ -443,8 +423,7 @@ public abstract class MediaOutputBaseAdapter extends mItemLayout.setBackground(backgroundDrawable); mItemLayout.setBackgroundTintList( ColorStateList.valueOf(mController.getColorConnectedItemBackground())); - mIconAreaLayout.setBackgroundTintList( - ColorStateList.valueOf(mController.getColorConnectedItemBackground())); + mIconAreaLayout.setBackground(null); } private void initAnimator() { diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 67fe0e981b09..1a5e605c96f8 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java @@ -79,7 +79,8 @@ public class SysUiState implements Dumpable { /** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */ public SysUiState setFlag(@SystemUiStateFlags long flag, boolean enabled) { - final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag); + final Boolean overrideOrNull = mSceneContainerPlugin != null + ? mSceneContainerPlugin.flagValueOverride(flag) : null; if (overrideOrNull != null && enabled != overrideOrNull) { if (DEBUG) { Log.d(TAG, "setFlag for flag " + flag + " and value " + enabled + " overridden to " diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 0de8c40bddaa..1807847e3f3c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -77,7 +77,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.Flags; @@ -115,7 +115,7 @@ public final class NavBarHelper implements AccessibilityButtonModeObserver.ModeChangedListener, AccessibilityButtonTargetsObserver.TargetsChangedListener, AccessibilityGestureTargetsObserver.TargetsChangedListener, - OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, + LauncherProxyService.LauncherProxyListener, NavigationModeController.ModeChangedListener, Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener { private static final String TAG = NavBarHelper.class.getSimpleName(); @@ -199,7 +199,7 @@ public final class NavBarHelper implements AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, AccessibilityGestureTargetsObserver accessibilityGestureTargetsObserver, SystemActions systemActions, - OverviewProxyService overviewProxyService, + LauncherProxyService launcherProxyService, Lazy<AssistManager> assistManagerLazy, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, KeyguardStateController keyguardStateController, @@ -240,7 +240,7 @@ public final class NavBarHelper implements mNavBarMode = navigationModeController.addListener(this); mCommandQueue.addCallback(this); configurationController.addCallback(this); - overviewProxyService.addCallback(this); + launcherProxyService.addCallback(this); dumpManager.registerDumpable(this); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 645bd0b4b441..ebda3765cf90 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -52,7 +52,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.views.NavigationBar; import com.android.systemui.navigationbar.views.NavigationBarView; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -115,7 +115,7 @@ public class NavigationBarControllerImpl implements @Inject public NavigationBarControllerImpl(Context context, - OverviewProxyService overviewProxyService, + LauncherProxyService launcherProxyService, NavigationModeController navigationModeController, SysUiState sysUiFlagsContainer, CommandQueue commandQueue, @@ -145,7 +145,7 @@ public class NavigationBarControllerImpl implements mNavMode = navigationModeController.addListener(this); mNavBarHelper = navBarHelper; mTaskbarDelegate = taskbarDelegate; - mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, + mTaskbarDelegate.setDependencies(commandQueue, launcherProxyService, navBarHelper, navigationModeController, sysUiFlagsContainer, dumpManager, autoHideControllerStore.forDisplay(mContext.getDisplayId()), lightBarController, pipOptional, backAnimation.orElse(null), diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index c35a5a96b98c..9d8943052b38 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -70,7 +70,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.statusbar.phone.BarTransitions; @@ -96,7 +96,7 @@ import javax.inject.Inject; /** */ @SysUISingleton public class TaskbarDelegate implements CommandQueue.Callbacks, - OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, + LauncherProxyService.LauncherProxyListener, NavigationModeController.ModeChangedListener, Dumpable { private static final String TAG = TaskbarDelegate.class.getSimpleName(); @@ -104,7 +104,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final LightBarTransitionsController.Factory mLightBarTransitionsControllerFactory; private boolean mInitialized; private CommandQueue mCommandQueue; - private OverviewProxyService mOverviewProxyService; + private LauncherProxyService mLauncherProxyService; private NavBarHelper mNavBarHelper; private NavigationModeController mNavigationModeController; private SysUiState mSysUiState; @@ -210,7 +210,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } public void setDependencies(CommandQueue commandQueue, - OverviewProxyService overviewProxyService, + LauncherProxyService launcherProxyService, NavBarHelper navBarHelper, NavigationModeController navigationModeController, SysUiState sysUiState, DumpManager dumpManager, @@ -222,7 +222,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, DisplayTracker displayTracker) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; - mOverviewProxyService = overviewProxyService; + mLauncherProxyService = launcherProxyService; mNavBarHelper = navBarHelper; mNavigationModeController = navigationModeController; mSysUiState = sysUiState; @@ -240,12 +240,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void onDisplayReady(int displayId) { CommandQueue.Callbacks.super.onDisplayReady(displayId); - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().onDisplayReady(displayId); + mLauncherProxyService.getProxy().onDisplayReady(displayId); } catch (RemoteException e) { Log.e(TAG, "onDisplayReady() failed", e); } @@ -254,12 +254,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void onDisplayRemoved(int displayId) { CommandQueue.Callbacks.super.onDisplayRemoved(displayId); - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().onDisplayRemoved(displayId); + mLauncherProxyService.getProxy().onDisplayRemoved(displayId); } catch (RemoteException e) { Log.e(TAG, "onDisplayRemoved() failed", e); } @@ -268,12 +268,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void onDisplayRemoveSystemDecorations(int displayId) { CommandQueue.Callbacks.super.onDisplayRemoveSystemDecorations(displayId); - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().onDisplayRemoveSystemDecorations(displayId); + mLauncherProxyService.getProxy().onDisplayRemoveSystemDecorations(displayId); } catch (RemoteException e) { Log.e(TAG, "onDisplaySystemDecorationsRemoved() failed", e); } @@ -287,7 +287,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void applyDarkIntensity(float darkIntensity) { mBgHandler.post(() -> { - mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); + mLauncherProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); }); } @@ -309,7 +309,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mDefaultDisplayId = displayId; parseCurrentSysuiState(); mCommandQueue.addCallback(this); - mOverviewProxyService.addCallback(this); + mLauncherProxyService.addCallback(this); onNavigationModeChanged(mNavigationModeController.addListener(this)); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); // Initialize component callback @@ -334,7 +334,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, return; } mCommandQueue.removeCallback(this); - mOverviewProxyService.removeCallback(this); + mLauncherProxyService.removeCallback(this); mNavigationModeController.removeListener(this); mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mScreenPinningNotify = null; @@ -401,43 +401,43 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } void onTransitionModeUpdated(int barMode, boolean checkBarModes) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().onTransitionModeUpdated(barMode, checkBarModes); + mLauncherProxyService.getProxy().onTransitionModeUpdated(barMode, checkBarModes); } catch (RemoteException e) { Log.e(TAG, "onTransitionModeUpdated() failed, barMode: " + barMode, e); } } void checkNavBarModes(int displayId) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().checkNavBarModes(displayId); + mLauncherProxyService.getProxy().checkNavBarModes(displayId); } catch (RemoteException e) { Log.e(TAG, "checkNavBarModes() failed", e); } } void finishBarAnimations(int displayId) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().finishBarAnimations(displayId); + mLauncherProxyService.getProxy().finishBarAnimations(displayId); } catch (RemoteException e) { Log.e(TAG, "finishBarAnimations() failed", e); } } void touchAutoDim(int displayId) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } @@ -445,31 +445,31 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, int state = mStatusBarStateController.getState(); boolean shouldReset = state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED; - mOverviewProxyService.getProxy().touchAutoDim(displayId, shouldReset); + mLauncherProxyService.getProxy().touchAutoDim(displayId, shouldReset); } catch (RemoteException e) { Log.e(TAG, "touchAutoDim() failed", e); } } void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode, boolean animate) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().transitionTo(displayId, barMode, animate); + mLauncherProxyService.getProxy().transitionTo(displayId, barMode, animate); } catch (RemoteException e) { Log.e(TAG, "transitionTo() failed, barMode: " + barMode, e); } } private void updateAssistantAvailability(boolean assistantAvailable, boolean longPressHomeEnabled) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable, + mLauncherProxyService.getProxy().onAssistantAvailable(assistantAvailable, longPressHomeEnabled); } catch (RemoteException e) { Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e); @@ -477,24 +477,24 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } private void updateWallpaperVisible(int displayId, boolean visible) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().updateWallpaperVisibility(displayId, visible); + mLauncherProxyService.getProxy().updateWallpaperVisibility(displayId, visible); } catch (RemoteException e) { Log.e(TAG, "updateWallpaperVisibility() failed, visible: " + visible, e); } } private void appTransitionPending(boolean pending) { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().appTransitionPending(pending); + mLauncherProxyService.getProxy().appTransitionPending(pending); } catch (RemoteException e) { Log.e(TAG, "appTransitionPending() failed, pending: " + pending, e); } @@ -528,14 +528,14 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void onRotationProposal(int rotation, boolean isValid) { - mOverviewProxyService.onRotationProposal(rotation, isValid); + mLauncherProxyService.onRotationProposal(rotation, isValid); } @Override public void disable(int displayId, int state1, int state2, boolean animate) { mDisabledFlags = state1; updateSysuiFlags(); - mOverviewProxyService.disable(displayId, state1, state2, animate); + mLauncherProxyService.disable(displayId, state1, state2, animate); } @Override @@ -543,7 +543,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior, @InsetsType int requestedVisibleTypes, String packageName, LetterboxDetails[] letterboxDetails) { - mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior); + mLauncherProxyService.onSystemBarAttributesChanged(displayId, behavior); boolean nbModeChanged = false; if (mAppearance != appearance) { mAppearance = appearance; @@ -596,12 +596,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void toggleTaskbar() { - if (mOverviewProxyService.getProxy() == null) { + if (mLauncherProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().onTaskbarToggled(); + mLauncherProxyService.getProxy().onTaskbarToggled(); } catch (RemoteException e) { Log.e(TAG, "onTaskbarToggled() failed", e); } @@ -673,7 +673,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { - mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable); + mLauncherProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 1c94f56f0942..f44c2c01951c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -85,7 +85,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -155,8 +155,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } }; - private OverviewProxyService.OverviewProxyListener mQuickSwitchListener = - new OverviewProxyService.OverviewProxyListener() { + private LauncherProxyService.LauncherProxyListener mQuickSwitchListener = + new LauncherProxyService.LauncherProxyListener() { @Override public void onPrioritizedRotation(@Surface.Rotation int rotation) { mStartingQuickstepRotation = rotation; @@ -197,7 +197,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final Context mContext; private final UserTracker mUserTracker; - private final OverviewProxyService mOverviewProxyService; + private final LauncherProxyService mLauncherProxyService; private final SysUiState mSysUiState; private Runnable mStateChangeCallback; private Consumer<Boolean> mButtonForcedVisibleCallback; @@ -332,7 +332,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED); if (!mInRejectedExclusion) { // Log successful back gesture to contextual edu stats - mOverviewProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe, + mLauncherProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe, GestureType.BACK); } } @@ -441,7 +441,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack @AssistedInject EdgeBackGestureHandler( @Assisted Context context, - OverviewProxyService overviewProxyService, + LauncherProxyService launcherProxyService, SysUiState sysUiState, PluginManager pluginManager, @BackPanelUiThread UiThreadContext uiThreadContext, @@ -468,7 +468,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mBackgroundExecutor = backgroundExecutor; mBgHandler = bgHandler; mUserTracker = userTracker; - mOverviewProxyService = overviewProxyService; + mLauncherProxyService = launcherProxyService; mSysUiState = sysUiState; mPluginManager = pluginManager; mNavigationModeController = navigationModeController; @@ -620,7 +620,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack */ public void onNavBarAttached() { mIsAttached = true; - mOverviewProxyService.addCallback(mQuickSwitchListener); + mLauncherProxyService.addCallback(mQuickSwitchListener); mSysUiState.addCallback(mSysUiStateCallback); mInputManager.registerInputDeviceListener(mInputDeviceListener, mBgHandler); int[] inputDevices = mInputManager.getInputDeviceIds(); @@ -636,7 +636,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack */ public void onNavBarDetached() { mIsAttached = false; - mOverviewProxyService.removeCallback(mQuickSwitchListener); + mLauncherProxyService.removeCallback(mQuickSwitchListener); mSysUiState.removeCallback(mSysUiStateCallback); mInputManager.unregisterInputDeviceListener(mInputDeviceListener); mTrackpadsConnected.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index 692b1f341c94..f95f45906b23 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -36,7 +36,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; import static com.android.systemui.navigationbar.NavBarHelper.transitionMode; -import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; +import static com.android.systemui.recents.LauncherProxyService.LauncherProxyListener; import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import static com.android.systemui.shared.rotation.RotationButtonController.DEBUG_ROTATION; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE; @@ -131,7 +131,7 @@ import com.android.systemui.navigationbar.views.buttons.KeyButtonView; import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger; import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; @@ -212,7 +212,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final ShadeViewController mShadeViewController; private final PanelExpansionInteractor mPanelExpansionInteractor; private final NotificationRemoteInputManager mNotificationRemoteInputManager; - private final OverviewProxyService mOverviewProxyService; + private final LauncherProxyService mLauncherProxyService; private final NavigationModeController mNavigationModeController; private final UserTracker mUserTracker; private final CommandQueue mCommandQueue; @@ -283,7 +283,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements * gesture to indicate to them that they can continue in that orientation without having to * rotate the phone * The secondary handle will show when we get - * {@link OverviewProxyListener#notifyPrioritizedRotation(int)} callback with the + * {@link LauncherProxyListener#notifyPrioritizedRotation(int)} callback with the * original handle hidden and we'll flip the visibilities once the * {@link #mTasksFrozenListener} fires */ @@ -387,12 +387,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; - private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { + private final LauncherProxyListener mLauncherProxyListener = new LauncherProxyListener() { @Override public void onConnectionChanged(boolean isConnected) { - mView.onOverviewProxyConnectionChange( - mOverviewProxyService.isEnabled()); - mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI()); + mView.onLauncherProxyConnectionChange( + mLauncherProxyService.isEnabled()); + mView.setShouldShowSwipeUpUi(mLauncherProxyService.shouldShowSwipeUpUI()); updateScreenPinningGestures(); } @@ -565,7 +565,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements AccessibilityManager accessibilityManager, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, - OverviewProxyService overviewProxyService, + LauncherProxyService launcherProxyService, NavigationModeController navigationModeController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, @@ -618,7 +618,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mShadeViewController = shadeViewController; mPanelExpansionInteractor = panelExpansionInteractor; mNotificationRemoteInputManager = notificationRemoteInputManager; - mOverviewProxyService = overviewProxyService; + mLauncherProxyService = launcherProxyService; mNavigationModeController = navigationModeController; mUserTracker = userTracker; mCommandQueue = commandQueue; @@ -827,7 +827,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements setNavBarMode(mNavBarMode); repositionNavigationBar(mCurrentRotation); mView.setUpdateActiveTouchRegionsCallback( - () -> mOverviewProxyService.onActiveNavBarRegionChanges( + () -> mLauncherProxyService.onActiveNavBarRegionChanges( getButtonLocations(true /* inScreen */, true /* useNearestRegion */))); mView.getViewTreeObserver().addOnComputeInternalInsetsListener( @@ -843,7 +843,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mWakefulnessLifecycle.addObserver(mWakefulnessObserver); notifyNavigationBarScreenOn(); - mOverviewProxyService.addCallback(mOverviewProxyListener); + mLauncherProxyService.addCallback(mLauncherProxyListener); updateSystemUiStateFlags(); // Currently there is no accelerometer sensor on non-default display. @@ -881,7 +881,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements public void onViewDetached() { mView.setUpdateActiveTouchRegionsCallback(null); getBarTransitions().destroy(); - mOverviewProxyService.removeCallback(mOverviewProxyListener); + mLauncherProxyService.removeCallback(mLauncherProxyListener); mUserTracker.removeCallback(mUserChangedCallback); mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); if (mOrientationHandle != null) { @@ -1699,9 +1699,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void updateAssistantEntrypoints(boolean assistantAvailable, boolean longPressHomeEnabled) { - if (mOverviewProxyService.getProxy() != null) { + if (mLauncherProxyService.getProxy() != null) { try { - mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable, + mLauncherProxyService.getProxy().onAssistantAvailable(assistantAvailable, longPressHomeEnabled); } catch (RemoteException e) { Log.w(TAG, "Unable to send assistant availability data to launcher"); @@ -2108,7 +2108,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (!canShowSecondaryHandle()) { resetSecondaryHandle(); } - mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI()); + mView.setShouldShowSwipeUpUi(mLauncherProxyService.shouldShowSwipeUpUI()); } }; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java index 96b730c08397..2c5a9c84645b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java @@ -41,7 +41,7 @@ import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.views.buttons.KeyButtonView; import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout; import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout.ReverseRelativeLayout; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.res.R; import com.android.systemui.shared.system.QuickStepContract; @@ -117,13 +117,13 @@ public class NavigationBarInflaterView extends FrameLayout { private boolean mIsVertical; private boolean mAlternativeOrder; - private OverviewProxyService mOverviewProxyService; + private LauncherProxyService mLauncherProxyService; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; public NavigationBarInflaterView(Context context, AttributeSet attrs) { super(context, attrs); createInflaters(); - mOverviewProxyService = Dependency.get(OverviewProxyService.class); + mLauncherProxyService = Dependency.get(LauncherProxyService.class); mListener = new Listener(this); mNavBarMode = Dependency.get(NavigationModeController.class).addListener(mListener); } @@ -159,7 +159,7 @@ public class NavigationBarInflaterView extends FrameLayout { protected String getDefaultLayout() { final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode) ? R.string.config_navBarLayoutHandle - : mOverviewProxyService.shouldShowSwipeUpUI() + : mLauncherProxyService.shouldShowSwipeUpUI() ? R.string.config_navBarLayoutQuickstep : R.string.config_navBarLayout; return getContext().getString(defaultResource); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java index 21366847bc84..36cb8fa374b0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java @@ -180,7 +180,7 @@ public class NavigationBarView extends FrameLayout { */ private final boolean mImeCanRenderGesturalNavButtons = canImeRenderGesturalNavButtons(); private Gefingerpoken mTouchHandler; - private boolean mOverviewProxyEnabled; + private boolean mLauncherProxyEnabled; private boolean mShowSwipeUpUi; private UpdateActiveTouchRegionsCallback mUpdateActiveTouchRegionsCallback; @@ -642,7 +642,7 @@ public class NavigationBarView extends FrameLayout { // When screen pinning, don't hide back and home when connected service or back and // recents buttons when disconnected from launcher service in screen pinning mode, // as they are used for exiting. - if (mOverviewProxyEnabled) { + if (mLauncherProxyEnabled) { // Force disable recents when not in legacy mode disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode); if (mScreenPinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) { @@ -764,8 +764,8 @@ public class NavigationBarView extends FrameLayout { } } - void onOverviewProxyConnectionChange(boolean enabled) { - mOverviewProxyEnabled = enabled; + void onLauncherProxyConnectionChange(boolean enabled) { + mLauncherProxyEnabled = enabled; } void setShouldShowSwipeUpUi(boolean showSwipeUpUi) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java index 111a2d43f881..32a03e5b10e9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java @@ -61,7 +61,7 @@ import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.assist.AssistManager; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.res.R; import com.android.systemui.shared.navigationbar.KeyButtonRipple; import com.android.systemui.shared.system.QuickStepContract; @@ -82,7 +82,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @VisibleForTesting boolean mLongClicked; private OnClickListener mOnClickListener; private final KeyButtonRipple mRipple; - private final OverviewProxyService mOverviewProxyService; + private final LauncherProxyService mLauncherProxyService; private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final InputManagerGlobal mInputManagerGlobal; private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); @@ -181,7 +181,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mRipple = new KeyButtonRipple(context, this, R.dimen.key_button_ripple_max_width); - mOverviewProxyService = Dependency.get(OverviewProxyService.class); + mLauncherProxyService = Dependency.get(LauncherProxyService.class); mInputManagerGlobal = manager; setBackground(mRipple); setWillNotDraw(false); @@ -282,7 +282,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public boolean onTouchEvent(MotionEvent ev) { - final boolean showSwipeUI = mOverviewProxyService.shouldShowSwipeUpUI(); + final boolean showSwipeUI = mLauncherProxyService.shouldShowSwipeUpUI(); final int action = ev.getAction(); int x, y; if (action == MotionEvent.ACTION_DOWN) { diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt index 195b0cebe2eb..1b9251061f3d 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt @@ -21,7 +21,8 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult.HideOverlay -import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel @@ -38,7 +39,10 @@ class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() : Swipe.Up to HideOverlay(Overlays.NotificationsShade), Back to HideOverlay(Overlays.NotificationsShade), Swipe.Down(fromSource = SceneContainerEdge.TopRight) to - ReplaceByOverlay(Overlays.QuickSettingsShade), + ShowOverlay( + Overlays.QuickSettingsShade, + hideCurrentOverlays = HideCurrentOverlays.Some(Overlays.NotificationsShade), + ), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index afb852ae824c..c8f7be6d80b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import static com.android.systemui.Flags.quickSettingsVisualHapticsLongpress; import android.annotation.NonNull; import android.annotation.Nullable; @@ -364,12 +363,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr } private void addTile(final QSTile tile, boolean collapsedView) { - QSLongPressEffect longPressEffect; - if (quickSettingsVisualHapticsLongpress()) { - longPressEffect = mLongPressEffectProvider.get(); - } else { - longPressEffect = null; - } + QSLongPressEffect longPressEffect = mLongPressEffectProvider.get(); final QSTileViewImpl tileView = new QSTileViewImpl( getContext(), collapsedView, longPressEffect); final TileRecord r = new TileRecord(tile, tileView); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt index 6d3e5d07c251..b1f99cccff70 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt @@ -61,6 +61,7 @@ constructor( private val internetDialogManager: InternetDialogManager, private val wifiStateWorker: WifiStateWorker, private val accessPointController: AccessPointController, + private val internetDetailsViewModelFactory: InternetDetailsViewModel.Factory, ) : QSTileImpl<QSTile.BooleanState>( host, @@ -107,7 +108,7 @@ constructor( } override fun getDetailsViewModel(): TileDetailsViewModel { - return InternetDetailsViewModel { longClick(null) } + return internetDetailsViewModelFactory.create { longClick(null) } } override fun handleSecondaryClick(expandable: Expandable?) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt index c64532a2c4ba..733159e285e8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt @@ -57,10 +57,8 @@ import com.android.settingslib.satellite.SatelliteDialogUtils.mayStartSatelliteW import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils import com.android.systemui.Prefs import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan -import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController @@ -75,11 +73,7 @@ import kotlinx.coroutines.Job /** * View content for the Internet tile details that handles all UI interactions and state management. - * - * @param internetDialog non-null if the details should be shown as part of a dialog and null - * otherwise. */ -// TODO: b/377388104 Make this content for details view only. class InternetDetailsContentManager @AssistedInject constructor( @@ -88,9 +82,7 @@ constructor( @Assisted(CAN_CONFIG_WIFI) private val canConfigWifi: Boolean, @Assisted private val coroutineScope: CoroutineScope, @Assisted private var context: Context, - @Assisted private var internetDialog: SystemUIDialog?, private val uiEventLogger: UiEventLogger, - private val dialogTransitionAnimator: DialogTransitionAnimator, @Main private val handler: Handler, @Background private val backgroundExecutor: Executor, private val keyguard: KeyguardStateController, @@ -104,8 +96,6 @@ constructor( // UI Components private lateinit var contentView: View - private lateinit var internetDialogTitleView: TextView - private lateinit var internetDialogSubTitleView: TextView private lateinit var divider: View private lateinit var progressBar: ProgressBar private lateinit var ethernetLayout: LinearLayout @@ -132,7 +122,6 @@ constructor( private lateinit var shareWifiButton: Button private lateinit var airplaneModeButton: Button private var alertDialog: AlertDialog? = null - private lateinit var doneButton: Button private val canChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context) @@ -153,7 +142,6 @@ constructor( @Assisted(CAN_CONFIG_WIFI) canConfigWifi: Boolean, coroutineScope: CoroutineScope, context: Context, - internetDialog: SystemUIDialog?, ): InternetDetailsContentManager } @@ -209,8 +197,6 @@ constructor( } // Network layouts - internetDialogTitleView = contentView.requireViewById(R.id.internet_dialog_title) - internetDialogSubTitleView = contentView.requireViewById(R.id.internet_dialog_subtitle) divider = contentView.requireViewById(R.id.divider) progressBar = contentView.requireViewById(R.id.wifi_searching_progress) @@ -219,15 +205,6 @@ constructor( setMobileLayout() ethernetLayout = contentView.requireViewById(R.id.ethernet_layout) - // Done button is only visible for the dialog view - doneButton = contentView.requireViewById(R.id.done_button) - if (internetDialog == null) { - doneButton.visibility = View.GONE - } else { - // Set done button if qs details view is not enabled. - doneButton.setOnClickListener { internetDialog!!.dismiss() } - } - // Share WiFi shareWifiButton = contentView.requireViewById(R.id.share_wifi_button) shareWifiButton.setOnClickListener { view -> @@ -251,6 +228,17 @@ constructor( // Background drawables backgroundOn = context.getDrawable(R.drawable.settingslib_switch_bar_bg_on) backgroundOff = context.getDrawable(R.drawable.internet_dialog_selected_effect) + + // Done button is only visible for the dialog view + contentView.findViewById<Button>(R.id.done_button).apply { visibility = View.GONE } + + // Title and subtitle will be added in the `TileDetails` + contentView.findViewById<TextView>(R.id.internet_dialog_title).apply { + visibility = View.GONE + } + contentView.findViewById<TextView>(R.id.internet_dialog_subtitle).apply { + visibility = View.GONE + } } private fun setWifiLayout() { @@ -336,21 +324,19 @@ constructor( } } - private fun getDialogTitleText(): CharSequence { - return internetDetailsContentController.getDialogTitleText() + fun getTitleText(): String { + return internetDetailsContentController.getDialogTitleText().toString() + } + + fun getSubtitleText(): String { + return internetDetailsContentController.getSubtitleText(isProgressBarVisible).toString() } private fun updateDetailsUI(internetContent: InternetContent) { if (DEBUG) { Log.d(TAG, "updateDetailsUI ") } - if (QsDetailedView.isEnabled) { - internetDialogTitleView.visibility = View.GONE - internetDialogSubTitleView.visibility = View.GONE - } else { - internetDialogTitleView.text = internetContent.internetDialogTitleString - internetDialogSubTitleView.text = internetContent.internetDialogSubTitle - } + airplaneModeButton.visibility = if (internetContent.isAirplaneModeEnabled) View.VISIBLE else View.GONE @@ -361,17 +347,11 @@ constructor( private fun getStartingInternetContent(): InternetContent { return InternetContent( - internetDialogTitleString = getDialogTitleText(), - internetDialogSubTitle = getSubtitleText(), isWifiEnabled = internetDetailsContentController.isWifiEnabled, isDeviceLocked = internetDetailsContentController.isDeviceLocked, ) } - private fun getSubtitleText(): String { - return internetDetailsContentController.getSubtitleText(isProgressBarVisible).toString() - } - @VisibleForTesting internal fun hideWifiViews() { setProgressBarVisible(false) @@ -393,7 +373,6 @@ constructor( progressBar.visibility = if (visible) View.VISIBLE else View.GONE progressBar.isIndeterminate = visible divider.visibility = if (visible) View.GONE else View.VISIBLE - internetDialogSubTitleView.text = getSubtitleText() } private fun showTurnOffAutoDataSwitchDialog(subId: Int) { @@ -418,12 +397,7 @@ constructor( SystemUIDialog.setShowForAllUsers(alertDialog, true) SystemUIDialog.registerDismissListener(alertDialog) SystemUIDialog.setWindowOnTop(alertDialog, keyguard.isShowing()) - if (QsDetailedView.isEnabled) { - alertDialog!!.show() - } else { - dialogTransitionAnimator.showFromDialog(alertDialog!!, internetDialog!!, null, false) - Log.e(TAG, "Internet dialog is shown with the refactor code") - } + alertDialog!!.show() } private fun shouldShowMobileDialog(): Boolean { @@ -466,11 +440,8 @@ constructor( SystemUIDialog.setShowForAllUsers(alertDialog, true) SystemUIDialog.registerDismissListener(alertDialog) SystemUIDialog.setWindowOnTop(alertDialog, keyguard.isShowing()) - if (QsDetailedView.isEnabled) { - alertDialog!!.show() - } else { - dialogTransitionAnimator.showFromDialog(alertDialog!!, internetDialog!!, null, false) - } + + alertDialog!!.show() } private fun onClickConnectedWifi(view: View?) { @@ -803,7 +774,6 @@ constructor( secondaryMobileNetworkLayout?.setOnClickListener(null) seeAllLayout.setOnClickListener(null) wifiToggle.setOnCheckedChangeListener(null) - doneButton.setOnClickListener(null) shareWifiButton.setOnClickListener(null) airplaneModeButton.setOnClickListener(null) internetDetailsContentController.onStop() @@ -825,8 +795,6 @@ constructor( private fun getInternetContent(shouldUpdateMobileNetwork: Boolean): InternetContent { return InternetContent( shouldUpdateMobileNetwork = shouldUpdateMobileNetwork, - internetDialogTitleString = getDialogTitleText(), - internetDialogSubTitle = getSubtitleText(), activeNetworkIsCellular = if (shouldUpdateMobileNetwork) internetDetailsContentController.activeNetworkIsCellular() @@ -924,10 +892,7 @@ constructor( if (DEBUG) { Log.d(TAG, "dismissDialog") } - if (internetDialog != null) { - internetDialog!!.dismiss() - internetDialog = null - } + // TODO: b/377388104 Close details view } override fun onAccessPointsChanged( @@ -967,8 +932,6 @@ constructor( @VisibleForTesting data class InternetContent( - val internetDialogTitleString: CharSequence, - val internetDialogSubTitle: CharSequence, val isAirplaneModeEnabled: Boolean = false, val hasEthernet: Boolean = false, val shouldUpdateMobileNetwork: Boolean = false, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt index f239a179d79a..df4dddbca9e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt @@ -16,44 +16,93 @@ package com.android.systemui.qs.tiles.dialog +import android.util.Log import android.view.LayoutInflater import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.res.R +import com.android.systemui.statusbar.connectivity.AccessPointController +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject -class InternetDetailsViewModel( - onLongClick: () -> Unit, +class InternetDetailsViewModel +@AssistedInject +constructor( + private val accessPointController: AccessPointController, + private val contentManagerFactory: InternetDetailsContentManager.Factory, + @Assisted private val onLongClick: () -> Unit, ) : TileDetailsViewModel() { - private val _onLongClick = onLongClick + private lateinit var internetDetailsContentManager: InternetDetailsContentManager @Composable override fun GetContentView() { + val coroutineScope = rememberCoroutineScope() + val context = LocalContext.current + + internetDetailsContentManager = remember { + contentManagerFactory.create( + canConfigMobileData = accessPointController.canConfigMobileData(), + canConfigWifi = accessPointController.canConfigWifi(), + coroutineScope = coroutineScope, + context = context, + ) + } AndroidView( modifier = Modifier.fillMaxWidth().fillMaxHeight(), factory = { context -> - // Inflate with the existing dialog xml layout - LayoutInflater.from(context) - .inflate(R.layout.internet_connectivity_dialog, null) - // TODO: b/377388104 - Implement the internet details view + // Inflate with the existing dialog xml layout and bind it with the manager + val view = + LayoutInflater.from(context) + .inflate(R.layout.internet_connectivity_dialog, null) + internetDetailsContentManager.bind(view) + + view + // TODO: b/377388104 - Polish the internet details view UI + }, + onRelease = { + internetDetailsContentManager.unBind() + if (DEBUG) { + Log.d(TAG, "onRelease") + } }, ) } override fun clickOnSettingsButton() { - _onLongClick() + onLongClick() } override fun getTitle(): String { + // TODO: b/377388104 make title and sub title mutable states of string + // by internetDetailsContentManager.getTitleText() + // TODO: test title change between airplane mode and not airplane mode // TODO: b/377388104 Update the placeholder text return "Internet" } override fun getSubTitle(): String { + // TODO: b/377388104 make title and sub title mutable states of string + // by internetDetailsContentManager.getSubtitleText() + // TODO: test subtitle change between airplane mode and not airplane mode // TODO: b/377388104 Update the placeholder text return "Tab a network to connect" } + + @AssistedFactory + interface Factory { + fun create(onLongClick: () -> Unit): InternetDetailsViewModel + } + + companion object { + private const val TAG = "InternetDetailsVModel" + private val DEBUG: Boolean = Log.isLoggable(TAG, Log.DEBUG) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt index c4f9515b819f..6e2c437b9c16 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt @@ -43,6 +43,7 @@ constructor( private val wifiStateWorker: WifiStateWorker, private val accessPointController: AccessPointController, private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val internetDetailsViewModelFactory: InternetDetailsViewModel.Factory, ) : QSTileUserActionInteractor<InternetTileModel> { override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit = @@ -70,7 +71,7 @@ constructor( } override val detailsViewModel: TileDetailsViewModel = - InternetDetailsViewModel { handleLongClick(null) } + internetDetailsViewModelFactory.create { handleLongClick(null) } private fun handleLongClick(expandable:Expandable?){ qsTileIntentUserActionHandler.handle( diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt index 000f7f8a7d31..5bc26f50f70f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt @@ -21,7 +21,8 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult.HideOverlay -import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay +import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge @@ -47,7 +48,11 @@ constructor(private val editModeViewModel: EditModeViewModel) : UserActionsViewM } put( Swipe.Down(fromSource = SceneContainerEdge.TopLeft), - ReplaceByOverlay(Overlays.NotificationsShade), + ShowOverlay( + Overlays.NotificationsShade, + hideCurrentOverlays = + HideCurrentOverlays.Some(Overlays.QuickSettingsShade), + ), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java index 60c2cca1ae8b..9af4630bf492 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java @@ -100,14 +100,14 @@ import com.android.systemui.navigationbar.views.NavigationBar; import com.android.systemui.navigationbar.views.NavigationBarView; import com.android.systemui.navigationbar.views.buttons.KeyButtonView; import com.android.systemui.process.ProcessWrapper; -import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; +import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.ShadeInteractor; -import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.ILauncherProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; @@ -135,16 +135,16 @@ import javax.inject.Inject; import javax.inject.Provider; /** - * Class to send information from overview to launcher with a binder. + * Class to send information from SysUI to Launcher with a binder. */ @SysUISingleton -public class OverviewProxyService implements CallbackController<OverviewProxyListener>, +public class LauncherProxyService implements CallbackController<LauncherProxyListener>, NavigationModeController.ModeChangedListener, Dumpable { @VisibleForTesting static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE"; - public static final String TAG_OPS = "OverviewProxyService"; + public static final String TAG_OPS = "LauncherProxyService"; private static final long BACKOFF_MILLIS = 1000; private static final long DEFERRED_CALLBACK_MILLIS = 5000; // Max backoff caps at 5 mins @@ -165,7 +165,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final Runnable mConnectionRunnable = () -> internalConnectToCurrentUser("runnable: startConnectionToCurrentUser"); private final ComponentName mRecentsComponentName; - private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>(); + private final List<LauncherProxyListener> mConnectionCallbacks = new ArrayList<>(); private final Intent mQuickStepIntent; private final ScreenshotHelper mScreenshotHelper; private final CommandQueue mCommandQueue; @@ -179,12 +179,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final BroadcastDispatcher mBroadcastDispatcher; private final BackAnimation mBackAnimation; - private IOverviewProxy mOverviewProxy; + private ILauncherProxy mLauncherProxy; private int mConnectionBackoffAttempts; private boolean mBound; private boolean mIsEnabled; - // This is set to false when the overview service is requested to be bound until it is notified - // that the previous service has been cleaned up in IOverviewProxy#onUnbind(). It is also set to + // This is set to false when the launcher service is requested to be bound until it is notified + // that the previous service has been cleaned up in ILauncherProxy#onUnbind(). It is also set to // true after a 1000ms timeout by mDeferredBindAfterTimedOutCleanup. private boolean mIsPrevServiceCleanedUp = true; @@ -341,7 +341,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) { verifyCallerAndClearCallingIdentityPostMain("updateContextualEduStats", - () -> mHandler.post(() -> OverviewProxyService.this.updateContextualEduStats( + () -> mHandler.post(() -> LauncherProxyService.this.updateContextualEduStats( isTrackpadGesture, GestureType.valueOf(gestureType)))); } @@ -504,7 +504,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void onReceive(Context context, Intent intent) { if (Objects.equals(intent.getAction(), Intent.ACTION_USER_UNLOCKED)) { if (keyguardPrivateNotifications()) { - // Start the overview connection to the launcher service + // Start the launcher connection to the launcher service // Connect if user hasn't connected yet if (getProxy() == null) { startConnectionToCurrentUser(); @@ -546,14 +546,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } }; - private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { + private final ServiceConnection mLauncherServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(TAG_OPS, "Overview proxy service connected"); + Log.d(TAG_OPS, "Launcher proxy service connected"); mConnectionBackoffAttempts = 0; mHandler.removeCallbacks(mDeferredConnectionCallback); try { - service.linkToDeath(mOverviewServiceDeathRcpt, 0); + service.linkToDeath(mLauncherServiceDeathRcpt, 0); } catch (RemoteException e) { // Failed to link to death (process may have died between binding and connecting), // just unbind the service for now and retry again @@ -564,7 +564,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } mCurrentBoundedUserId = mUserTracker.getUserId(); - mOverviewProxy = IOverviewProxy.Stub.asInterface(service); + mLauncherProxy = ILauncherProxy.Stub.asInterface(service); Bundle params = new Bundle(); addInterface(mSysUiProxy, params); @@ -574,8 +574,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mShellInterface.createExternalInterfaces(params); try { - Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy"); - mOverviewProxy.onInitialize(params); + Log.d(TAG_OPS, "LauncherProxyService connected, initializing launcher proxy"); + mLauncherProxy.onInitialize(params); } catch (RemoteException e) { mCurrentBoundedUserId = -1; Log.e(TAG_OPS, "Failed to call onInitialize()", e); @@ -614,7 +614,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged; // This is the death handler for the binder from the launcher service - private final IBinder.DeathRecipient mOverviewServiceDeathRcpt + private final IBinder.DeathRecipient mLauncherServiceDeathRcpt = this::cleanupAfterDeath; private final IVoiceInteractionSessionListener mVoiceInteractionSessionListener = @@ -632,7 +632,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void onVoiceSessionWindowVisibilityChanged(boolean visible) { mContext.getMainExecutor().execute(() -> - OverviewProxyService.this.onVoiceSessionWindowVisibilityChanged(visible)); + LauncherProxyService.this.onVoiceSessionWindowVisibilityChanged(visible)); } @Override @@ -652,7 +652,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject - public OverviewProxyService(Context context, + public LauncherProxyService(Context context, @Main Executor mainExecutor, CommandQueue commandQueue, ShellInterface shellInterface, @@ -755,14 +755,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) { - if (mOverviewProxy != null) { + if (mLauncherProxy != null) { try { if (DesktopModeStatus.canEnterDesktopMode(mContext) && (sysUiState.getFlags() & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) { return; } - mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop); + mLauncherProxy.enterStageSplitFromRunningApp(leftOrTop); } catch (RemoteException e) { Log.w(TAG_OPS, "Unable to enter stage split from the current running app"); } @@ -817,12 +817,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) { if (SysUiState.DEBUG) { - Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy=" - + mOverviewProxy + " flags=" + flags); + Log.d(TAG_OPS, "Notifying sysui state change to launcher service: proxy=" + + mLauncherProxy + " flags=" + flags); } try { - if (mOverviewProxy != null) { - mOverviewProxy.onSystemUiStateChanged(flags); + if (mLauncherProxy != null) { + mLauncherProxy.onSystemUiStateChanged(flags); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to notify sysui state change", e); @@ -854,9 +854,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } private void dispatchNavButtonBounds() { - if (mOverviewProxy != null && mActiveNavBarRegion != null) { + if (mLauncherProxy != null && mActiveNavBarRegion != null) { try { - mOverviewProxy.onActiveNavBarRegionChanges(mActiveNavBarRegion); + mLauncherProxy.onActiveNavBarRegionChanges(mActiveNavBarRegion); } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onActiveNavBarRegionChanges()", e); } @@ -888,7 +888,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // This should not happen, but if any per-user SysUI component has a dependency on OPS, // then this could get triggered Log.w(TAG_OPS, - "Skipping connection to overview service due to non-system foreground user " + "Skipping connection to launcher service due to non-system foreground user " + "caller"); return; } @@ -925,7 +925,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } try { mBound = mContext.bindServiceAsUser(mQuickStepIntent, - mOverviewServiceConnection, + mLauncherServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, currentUser); } catch (SecurityException e) { @@ -954,15 +954,15 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } @Override - public void addCallback(@NonNull OverviewProxyListener listener) { + public void addCallback(@NonNull LauncherProxyListener listener) { if (!mConnectionCallbacks.contains(listener)) { mConnectionCallbacks.add(listener); } - listener.onConnectionChanged(mOverviewProxy != null); + listener.onConnectionChanged(mLauncherProxy != null); } @Override - public void removeCallback(@NonNull OverviewProxyListener listener) { + public void removeCallback(@NonNull LauncherProxyListener listener) { mConnectionCallbacks.remove(listener); } @@ -974,21 +974,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return mIsEnabled; } - public IOverviewProxy getProxy() { - return mOverviewProxy; + public ILauncherProxy getProxy() { + return mLauncherProxy; } private void disconnectFromLauncherService(String disconnectReason) { Log.d(TAG_OPS, "disconnectFromLauncherService bound?: " + mBound + - " currentProxy: " + mOverviewProxy + " disconnectReason: " + disconnectReason, + " currentProxy: " + mLauncherProxy + " disconnectReason: " + disconnectReason, new Throwable()); if (mBound) { // Always unbind the service (ie. if called through onNullBinding or onBindingDied) - mContext.unbindService(mOverviewServiceConnection); + mContext.unbindService(mLauncherServiceConnection); mBound = false; - if (mOverviewProxy != null) { + if (mLauncherProxy != null) { try { - mOverviewProxy.onUnbind(new IRemoteCallback.Stub() { + mLauncherProxy.onUnbind(new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) throws RemoteException { // Received Launcher reply, try to bind anew. @@ -1006,9 +1006,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - if (mOverviewProxy != null) { - mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0); - mOverviewProxy = null; + if (mLauncherProxy != null) { + mLauncherProxy.asBinder().unlinkToDeath(mLauncherServiceDeathRcpt, 0); + mLauncherProxy = null; notifyConnectionChanged(); } } @@ -1044,7 +1044,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private void notifyConnectionChanged() { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { - mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null); + mConnectionCallbacks.get(i).onConnectionChanged(mLauncherProxy != null); } } @@ -1095,10 +1095,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void notifyAssistantVisibilityChanged(float visibility) { try { - if (mOverviewProxy != null) { - mOverviewProxy.onAssistantVisibilityChanged(visibility); + if (mLauncherProxy != null) { + mLauncherProxy.onAssistantVisibilityChanged(visibility); } else { - Log.e(TAG_OPS, "Failed to get overview proxy for assistant visibility."); + Log.e(TAG_OPS, "Failed to get launcher proxy for assistant visibility."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call notifyAssistantVisibilityChanged()", e); @@ -1148,10 +1148,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void disable(int displayId, int state1, int state2, boolean animate) { try { - if (mOverviewProxy != null) { - mOverviewProxy.disable(displayId, state1, state2, animate); + if (mLauncherProxy != null) { + mLauncherProxy.disable(displayId, state1, state2, animate); } else { - Log.e(TAG_OPS, "Failed to get overview proxy for disable flags."); + Log.e(TAG_OPS, "Failed to get launcher proxy for disable flags."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call disable()", e); @@ -1160,10 +1160,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void onRotationProposal(int rotation, boolean isValid) { try { - if (mOverviewProxy != null) { - mOverviewProxy.onRotationProposal(rotation, isValid); + if (mLauncherProxy != null) { + mLauncherProxy.onRotationProposal(rotation, isValid); } else { - Log.e(TAG_OPS, "Failed to get overview proxy for proposing rotation."); + Log.e(TAG_OPS, "Failed to get launcher proxy for proposing rotation."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onRotationProposal()", e); @@ -1172,10 +1172,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void onSystemBarAttributesChanged(int displayId, int behavior) { try { - if (mOverviewProxy != null) { - mOverviewProxy.onSystemBarAttributesChanged(displayId, behavior); + if (mLauncherProxy != null) { + mLauncherProxy.onSystemBarAttributesChanged(displayId, behavior); } else { - Log.e(TAG_OPS, "Failed to get overview proxy for system bar attr change."); + Log.e(TAG_OPS, "Failed to get launcher proxy for system bar attr change."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onSystemBarAttributesChanged()", e); @@ -1184,10 +1184,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void onNavButtonsDarkIntensityChanged(float darkIntensity) { try { - if (mOverviewProxy != null) { - mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity); + if (mLauncherProxy != null) { + mLauncherProxy.onNavButtonsDarkIntensityChanged(darkIntensity); } else { - Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity"); + Log.e(TAG_OPS, "Failed to get launcher proxy to update nav buttons dark intensity"); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e); @@ -1196,10 +1196,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { try { - if (mOverviewProxy != null) { - mOverviewProxy.onNavigationBarLumaSamplingEnabled(displayId, enable); + if (mLauncherProxy != null) { + mLauncherProxy.onNavigationBarLumaSamplingEnabled(displayId, enable); } else { - Log.e(TAG_OPS, "Failed to get overview proxy to enable/disable nav bar luma" + Log.e(TAG_OPS, "Failed to get launcher proxy to enable/disable nav bar luma" + "sampling"); } } catch (RemoteException e) { @@ -1221,7 +1221,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void dump(PrintWriter pw, String[] args) { pw.println(TAG_OPS + " state:"); - pw.print(" isConnected="); pw.println(mOverviewProxy != null); + pw.print(" isConnected="); pw.println(mLauncherProxy != null); pw.print(" mIsEnabled="); pw.println(isEnabled()); pw.print(" mRecentsComponentName="); pw.println(mRecentsComponentName); pw.print(" mQuickStepIntent="); pw.println(mQuickStepIntent); @@ -1237,7 +1237,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mSysUiState.dump(pw, args); } - public interface OverviewProxyListener { + public interface LauncherProxyListener { default void onConnectionChanged(boolean isConnected) {} default void onPrioritizedRotation(@Surface.Rotation int rotation) {} default void onOverviewShown(boolean fromHome) {} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java index 21c5ae8aec40..e51b73dd96c6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java @@ -23,30 +23,30 @@ import android.util.Log; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.ILauncherProxy; import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; /** - * An implementation of the Recents interface which proxies to the OverviewProxyService. + * An implementation of the Recents interface which proxies to the LauncherProxyService. */ @SysUISingleton public class OverviewProxyRecentsImpl implements RecentsImplementation { private final static String TAG = "OverviewProxyRecentsImpl"; private Handler mHandler; - private final OverviewProxyService mOverviewProxyService; + private final LauncherProxyService mLauncherProxyService; private final ActivityStarter mActivityStarter; private final KeyguardStateController mKeyguardStateController; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject public OverviewProxyRecentsImpl( - OverviewProxyService overviewProxyService, + LauncherProxyService launcherProxyService, ActivityStarter activityStarter, KeyguardStateController keyguardStateController) { - mOverviewProxyService = overviewProxyService; + mLauncherProxyService = launcherProxyService; mActivityStarter = activityStarter; mKeyguardStateController = keyguardStateController; } @@ -58,10 +58,10 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { @Override public void showRecentApps(boolean triggeredFromAltTab) { - IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); - if (overviewProxy != null) { + ILauncherProxy launcherProxy = mLauncherProxyService.getProxy(); + if (launcherProxy != null) { try { - overviewProxy.onOverviewShown(triggeredFromAltTab); + launcherProxy.onOverviewShown(triggeredFromAltTab); } catch (RemoteException e) { Log.e(TAG, "Failed to send overview show event to launcher.", e); } @@ -70,10 +70,10 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { @Override public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { - IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); - if (overviewProxy != null) { + ILauncherProxy launcherProxy = mLauncherProxyService.getProxy(); + if (launcherProxy != null) { try { - overviewProxy.onOverviewHidden(triggeredFromAltTab, triggeredFromHomeKey); + launcherProxy.onOverviewHidden(triggeredFromAltTab, triggeredFromHomeKey); } catch (RemoteException e) { Log.e(TAG, "Failed to send overview hide event to launcher.", e); } @@ -83,13 +83,13 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { @Override public void toggleRecentApps() { // If connected to launcher service, let it handle the toggle logic - IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); - if (overviewProxy != null) { + ILauncherProxy launcherProxy = mLauncherProxyService.getProxy(); + if (launcherProxy != null) { final Runnable toggleRecents = () -> { try { - if (mOverviewProxyService.getProxy() != null) { - mOverviewProxyService.getProxy().onOverviewToggle(); - mOverviewProxyService.notifyToggleRecentApps(); + if (mLauncherProxyService.getProxy() != null) { + mLauncherProxyService.getProxy().onOverviewToggle(); + mLauncherProxyService.notifyToggleRecentApps(); } } catch (RemoteException e) { Log.e(TAG, "Cannot send toggle recents through proxy service.", e); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index a1b4def09ba9..28f5694c3332 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2251,7 +2251,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean isPanelVisibleBecauseOfHeadsUp() { - boolean headsUpVisible = mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway; + boolean headsUpVisible = (mHeadsUpManager != null && mHeadsUpManager.hasPinnedHeadsUp()) + || mHeadsUpAnimatingAway; return headsUpVisible && mBarState == StatusBarState.SHADE; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 7299f092640f..cf310dd32613 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -34,8 +34,8 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS import com.android.systemui.plugins.qs.QSContainerController -import com.android.systemui.recents.OverviewProxyService -import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.recents.LauncherProxyService +import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.system.QuickStepContract @@ -57,7 +57,7 @@ class NotificationsQSContainerController constructor( view: NotificationsQuickSettingsContainer, private val navigationModeController: NavigationModeController, - private val overviewProxyService: OverviewProxyService, + private val launcherProxyService: LauncherProxyService, private val shadeHeaderController: ShadeHeaderController, private val shadeInteractor: ShadeInteractor, private val fragmentService: FragmentService, @@ -85,8 +85,8 @@ constructor( private var isGestureNavigation = true private var taskbarVisible = false - private val taskbarVisibilityListener: OverviewProxyListener = - object : OverviewProxyListener { + private val taskbarVisibilityListener: LauncherProxyListener = + object : LauncherProxyListener { override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) { taskbarVisible = visible } @@ -134,7 +134,7 @@ constructor( public override fun onViewAttached() { updateResources() - overviewProxyService.addCallback(taskbarVisibilityListener) + launcherProxyService.addCallback(taskbarVisibilityListener) mView.setInsetsChangedListener(delayedInsetSetter) mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } mView.setConfigurationChangedListener { updateResources() } @@ -142,7 +142,7 @@ constructor( } override fun onViewDetached() { - overviewProxyService.removeCallback(taskbarVisibilityListener) + launcherProxyService.removeCallback(taskbarVisibilityListener) mView.removeOnInsetsChangedListener() mView.removeQSFragmentAttachedListener() mView.setConfigurationChangedListener(null) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index d82f8e722744..fa40aa2bad24 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -293,7 +293,7 @@ constructor( override fun onDensityOrFontScaleChanged() { clock.setTextAppearance(R.style.TextAppearance_QS_Status) date.setTextAppearance(R.style.TextAppearance_QS_Status) - mShadeCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers) + mShadeCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status) loadConstraints() header.minHeight = resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index c8f972774ab0..382fc7058bf0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -70,7 +70,7 @@ import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -152,7 +152,7 @@ public class NotificationLockscreenUserManagerImpl implements private final List<UserChangedListener> mListeners = new ArrayList<>(); private final BroadcastDispatcher mBroadcastDispatcher; private final NotificationClickNotifier mClickNotifier; - private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy; + private final Lazy<LauncherProxyService> mLauncherProxyServiceLazy; private final FeatureFlagsClassic mFeatureFlags; private boolean mShowLockscreenNotifications; private LockPatternUtils mLockPatternUtils; @@ -235,8 +235,8 @@ public class NotificationLockscreenUserManagerImpl implements if (!keyguardPrivateNotifications()) { // Start the overview connection to the launcher service // Connect if user hasn't connected yet - if (mOverviewProxyServiceLazy.get().getProxy() == null) { - mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + if (mLauncherProxyServiceLazy.get().getProxy() == null) { + mLauncherProxyServiceLazy.get().startConnectionToCurrentUser(); } } } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) { @@ -318,7 +318,7 @@ public class NotificationLockscreenUserManagerImpl implements Lazy<NotificationVisibilityProvider> visibilityProviderLazy, Lazy<CommonNotifCollection> commonNotifCollectionLazy, NotificationClickNotifier clickNotifier, - Lazy<OverviewProxyService> overviewProxyServiceLazy, + Lazy<LauncherProxyService> launcherProxyServiceLazy, KeyguardManager keyguardManager, StatusBarStateController statusBarStateController, @Main Executor mainExecutor, @@ -343,7 +343,7 @@ public class NotificationLockscreenUserManagerImpl implements mVisibilityProviderLazy = visibilityProviderLazy; mCommonNotifCollectionLazy = commonNotifCollectionLazy; mClickNotifier = clickNotifier; - mOverviewProxyServiceLazy = overviewProxyServiceLazy; + mLauncherProxyServiceLazy = launcherProxyServiceLazy; statusBarStateController.addCallback(this); mLockPatternUtils = lockPatternUtils; mKeyguardManager = keyguardManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index b7cad625b7b8..46456b841e3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti import com.android.systemui.statusbar.notification.domain.model.TopPinnedState import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -73,17 +74,24 @@ constructor( OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key) } val colors = this.promotedContent.toCustomColorsModel() - val onClickListener = + + val clickListener: () -> Unit = { + // The notification pipeline needs everything to run on the main thread, so keep + // this event on the main thread. + applicationScope.launch { + notifChipsInteractor.onPromotedNotificationChipTapped(this@toActivityChipModel.key) + } + } + val onClickListenerLegacy = View.OnClickListener { - // The notification pipeline needs everything to run on the main thread, so keep - // this event on the main thread. - applicationScope.launch { - notifChipsInteractor.onPromotedNotificationChipTapped( - this@toActivityChipModel.key - ) - } + StatusBarChipsModernization.assertInLegacyMode() + clickListener.invoke() } - val clickBehavior = OngoingActivityChipModel.ClickBehavior.None + val clickBehavior = + OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification({ + StatusBarChipsModernization.assertInNewMode() + clickListener.invoke() + }) val isShowingHeadsUpFromChipTap = headsUpState is TopPinnedState.Pinned && @@ -95,7 +103,7 @@ constructor( return OngoingActivityChipModel.Shown.IconOnly( icon, colors, - onClickListener, + onClickListenerLegacy, clickBehavior, ) } @@ -105,7 +113,7 @@ constructor( icon, colors, this.promotedContent.shortCriticalText, - onClickListener, + onClickListenerLegacy, clickBehavior, ) } @@ -121,7 +129,7 @@ constructor( return OngoingActivityChipModel.Shown.IconOnly( icon, colors, - onClickListener, + onClickListenerLegacy, clickBehavior, ) } @@ -130,7 +138,7 @@ constructor( return OngoingActivityChipModel.Shown.IconOnly( icon, colors, - onClickListener, + onClickListenerLegacy, clickBehavior, ) } @@ -140,7 +148,7 @@ constructor( icon, colors, time = this.promotedContent.time.time, - onClickListener, + onClickListenerLegacy, clickBehavior, ) } @@ -149,7 +157,7 @@ constructor( icon, colors, startTimeMs = this.promotedContent.time.time, - onClickListener, + onClickListenerLegacy, clickBehavior, ) } @@ -159,7 +167,7 @@ constructor( icon, colors, startTimeMs = this.promotedContent.time.time, - onClickListener, + onClickListenerLegacy, clickBehavior, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index a682f9674e2e..279792ef7536 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -66,6 +66,9 @@ fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifie ChipBody(model, onClick = { clickBehavior.onClick(expandable) }) } } + is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> { + ChipBody(model, onClick = { clickBehavior.onClick() }) + } is OngoingActivityChipModel.ClickBehavior.None -> { ChipBody(model, modifier = modifier) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 68c8f8cb4254..c6d6da2ad9aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -170,5 +170,8 @@ sealed class OngoingActivityChipModel { /** The chip expands into a dialog or activity on click. */ data class ExpandAction(val onClick: (Expandable) -> Unit) : ClickBehavior + + /** Clicking the chip will show the heads up notification associated with the chip. */ + data class ShowHeadsUpNotification(val onClick: () -> Unit) : ClickBehavior } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 3825c098ca5d..b6ef95893036 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification import android.app.Notification +import android.app.Notification.EXTRA_SUMMARIZED_CONTENT import android.content.Context import android.content.pm.LauncherApps import android.graphics.drawable.AnimatedImageDrawable @@ -66,6 +67,12 @@ constructor( messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo) shortcutInfo.label?.let { label -> messagingStyle.conversationTitle = label } } + if (NmSummarizationUiFlag.isEnabled) { + entry.sbn.notification.extras.putCharSequence( + EXTRA_SUMMARIZED_CONTENT, entry.ranking.summarization + ) + } + messagingStyle.unreadMessageCount = conversationNotificationManager.getUnreadCount(entry, recoveredBuilder) return messagingStyle diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt new file mode 100644 index 000000000000..feac0a514828 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification + +import android.app.Flags; +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** + * Helper for android.app.nm_summarization and android.nm_summarization_ui. The new functionality + * should be enabled if either flag is enabled. + */ +@Suppress("NOTHING_TO_INLINE") +object NmSummarizationUiFlag { + const val FLAG_DESC = "android.app.nm_summarization(_ui)" + + @JvmStatic + inline val isEnabled + get() = Flags.nmSummarizationUi() || Flags.nmSummarization() + + /** + * 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_DESC) + + /** + * 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_DESC) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt index 331ef1c01596..aa5008b8416e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt @@ -40,6 +40,7 @@ internal constructor( @RedactionType val redactionType: Int, val isChildInGroup: Boolean, val isGroupSummary: Boolean, + val summarization: String?, ) { companion object { @JvmStatic @@ -61,6 +62,7 @@ internal constructor( AsyncGroupHeaderViewInflation.isEnabled && !oldAdjustment.isGroupSummary && newAdjustment.isGroupSummary -> true + oldAdjustment.summarization != newAdjustment.summarization -> true else -> false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt index 97e55c19d2f4..465bc288cbc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt @@ -152,5 +152,6 @@ constructor( }, isChildInGroup = entry.hasEverBeenGroupChild(), isGroupSummary = entry.hasEverBeenGroupSummary(), + summarization = entry.ranking.summarization ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java index 1ff0d9262476..92c10abff735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java @@ -288,14 +288,21 @@ public class HybridConversationNotificationView extends HybridNotificationView { public void setText( CharSequence titleText, CharSequence contentText, - CharSequence conversationSenderName + CharSequence conversationSenderName, + @Nullable String summarization ) { if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return; - if (conversationSenderName == null) { + if (summarization != null) { mConversationSenderName.setVisibility(GONE); + titleText = null; + contentText = summarization; } else { - mConversationSenderName.setVisibility(VISIBLE); - mConversationSenderName.setText(conversationSenderName); + if (conversationSenderName == null) { + mConversationSenderName.setVisibility(GONE); + } else { + mConversationSenderName.setVisibility(VISIBLE); + mConversationSenderName.setText(conversationSenderName); + } } // TODO (b/217799515): super.bind() doesn't use contentView, remove the contentView // argument when the flag is removed diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index c0dbb37c1b36..13ed6c449797 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -217,7 +217,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder messagingStyle, builder, row.getContext(), - false + false, + entry.getRanking().getSummarization() ); // If the messagingStyle is null, we want to inflate the normal view isConversation = viewModel.isConversation(); @@ -239,7 +240,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder messagingStyle, builder, row.getContext(), - true); + true, + entry.getRanking().getSummarization()); } else { result.mPublicInflatedSingleLineViewModel = SingleLineViewInflater.inflateRedactedSingleLineViewModel( @@ -1318,7 +1320,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder messagingStyle, recoveredBuilder, mContext, - false + false, + mEntry.getRanking().getSummarization() ); result.mInflatedSingleLineView = SingleLineViewInflater.inflatePrivateSingleLineView( @@ -1338,7 +1341,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder messagingStyle, recoveredBuilder, mContext, - true + true, + null ); } else { result.mPublicInflatedSingleLineViewModel = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index 0b299d965b09..f4aae6e288a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -719,6 +719,7 @@ constructor( builder = builder, systemUiContext = systemUiContext, redactText = false, + summarization = entry.ranking.summarization ) } else null @@ -735,6 +736,7 @@ constructor( builder = builder, systemUiContext = systemUiContext, redactText = true, + summarization = null ) } else { SingleLineViewInflater.inflateRedactedSingleLineViewModel( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt index fe2803bfc5d6..c051513ef3b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt @@ -61,6 +61,7 @@ internal object SingleLineViewInflater { builder: Notification.Builder, systemUiContext: Context, redactText: Boolean, + summarization: String? ): SingleLineViewModel { if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) { return SingleLineViewModel(null, null, null) @@ -108,6 +109,7 @@ internal object SingleLineViewInflater { conversationSenderName = if (isGroupConversation) conversationTextData?.senderName else null, avatar = conversationAvatar, + summarization = summarization ) return SingleLineViewModel( @@ -132,6 +134,7 @@ internal object SingleLineViewInflater { .ic_redacted_notification_single_line_icon ) ), + null ) } else { null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt index a17197c1f8ea..a50fc4c7986a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt @@ -32,6 +32,7 @@ object SingleLineViewBinder { viewModel?.titleText, viewModel?.contentText, viewModel?.conversationData?.conversationSenderName, + viewModel?.conversationData?.summarization ) } else { // bind the title and content text views diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt index d583fa5d97ed..32ded25f18a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt @@ -46,6 +46,7 @@ data class SingleLineViewModel( data class ConversationData( val conversationSenderName: CharSequence?, val avatar: ConversationAvatar, + val summarization: String? ) /** diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 69b7e892a380..9795cda97f37 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -49,8 +49,10 @@ import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton +import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.isFourFingerTouchpadSwipe import com.android.systemui.touchpad.tutorial.ui.gesture.isThreeFingerTouchpadSwipe @@ -80,6 +82,7 @@ fun TutorialSelectionScreen( } ), ) { + val padding = if (hasCompactWindowSize()) 24.dp else 60.dp val configuration = LocalConfiguration.current when (configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { @@ -88,7 +91,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(60.dp), + modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, ) } @@ -98,7 +101,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(60.dp), + modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, ) } @@ -106,7 +109,7 @@ fun TutorialSelectionScreen( // because other composables have weight 1, Done button will be positioned first DoneButton( onDoneButtonClicked = onDoneButtonClicked, - modifier = Modifier.padding(horizontal = 60.dp), + modifier = Modifier.padding(horizontal = padding), ) } } @@ -146,7 +149,7 @@ private fun VerticalSelectionButtons( lastSelectedScreen: Screen, ) { Column( - verticalArrangement = Arrangement.spacedBy(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { @@ -244,8 +247,13 @@ private fun TutorialButton( modifier = Modifier.width(30.dp).height(30.dp), tint = iconColor, ) - Spacer(modifier = Modifier.height(16.dp)) - Text(text = text, style = MaterialTheme.typography.headlineLarge, color = iconColor) + if (!hasCompactWindowSize()) Spacer(modifier = Modifier.height(16.dp)) + Text( + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineLarge, + color = iconColor, + ) } } } diff --git a/packages/SystemUI/src/com/google/android/systemui/lowlightclock/LowLightClockDreamService.java b/packages/SystemUI/src/com/google/android/systemui/lowlightclock/LowLightClockDreamService.java new file mode 100644 index 000000000000..8a5f7eaf8776 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/lowlightclock/LowLightClockDreamService.java @@ -0,0 +1,161 @@ +/* + * 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.systemui.lowlightclock; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.Nullable; +import android.service.dreams.DreamService; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextClock; +import android.widget.TextView; + +import com.android.dream.lowlight.LowLightTransitionCoordinator; +import com.android.systemui.lowlightclock.ChargingStatusProvider; +import com.android.systemui.lowlightclock.LowLightClockAnimationProvider; +import com.android.systemui.lowlightclock.LowLightDisplayController; +import com.android.systemui.res.R; + +import java.util.Optional; + +import javax.inject.Inject; +import javax.inject.Provider; + +/** + * A dark themed text clock dream to be shown when the device is in a low light environment. + */ +public class LowLightClockDreamService extends DreamService implements + LowLightTransitionCoordinator.LowLightExitListener { + private static final String TAG = "LowLightClockDreamService"; + + private final ChargingStatusProvider mChargingStatusProvider; + private final LowLightDisplayController mDisplayController; + private final LowLightClockAnimationProvider mAnimationProvider; + private final LowLightTransitionCoordinator mLowLightTransitionCoordinator; + private boolean mIsDimBrightnessSupported = false; + + private TextView mChargingStatusTextView; + private TextClock mTextClock; + @Nullable + private Animator mAnimationIn; + @Nullable + private Animator mAnimationOut; + + @Inject + public LowLightClockDreamService( + ChargingStatusProvider chargingStatusProvider, + LowLightClockAnimationProvider animationProvider, + LowLightTransitionCoordinator lowLightTransitionCoordinator, + Optional<Provider<LowLightDisplayController>> displayController) { + super(); + + mAnimationProvider = animationProvider; + mDisplayController = displayController.map(Provider::get).orElse(null); + mChargingStatusProvider = chargingStatusProvider; + mLowLightTransitionCoordinator = lowLightTransitionCoordinator; + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + setInteractive(false); + setFullscreen(true); + + setContentView(LayoutInflater.from(getApplicationContext()).inflate( + R.layout.low_light_clock_dream, null)); + + mTextClock = findViewById(R.id.low_light_text_clock); + + mChargingStatusTextView = findViewById(R.id.charging_status_text_view); + + mChargingStatusProvider.startUsing(this::updateChargingMessage); + + mLowLightTransitionCoordinator.setLowLightExitListener(this); + } + + @Override + public void onDreamingStarted() { + mAnimationIn = mAnimationProvider.provideAnimationIn(mTextClock, mChargingStatusTextView); + mAnimationIn.start(); + + if (mDisplayController != null) { + mIsDimBrightnessSupported = mDisplayController.isDisplayBrightnessModeSupported(); + + if (mIsDimBrightnessSupported) { + Log.v(TAG, "setting dim brightness state"); + mDisplayController.setDisplayBrightnessModeEnabled(true); + } else { + Log.v(TAG, "dim brightness not supported"); + } + } + } + + @Override + public void onDreamingStopped() { + if (mIsDimBrightnessSupported) { + Log.v(TAG, "clearing dim brightness state"); + mDisplayController.setDisplayBrightnessModeEnabled(false); + } + } + + @Override + public void onWakeUp() { + if (mAnimationIn != null) { + mAnimationIn.cancel(); + } + mAnimationOut = mAnimationProvider.provideAnimationOut(mTextClock, mChargingStatusTextView); + mAnimationOut.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + LowLightClockDreamService.super.onWakeUp(); + } + }); + mAnimationOut.start(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAnimationOut != null) { + mAnimationOut.cancel(); + } + + mChargingStatusProvider.stopUsing(); + + mLowLightTransitionCoordinator.setLowLightExitListener(null); + } + + private void updateChargingMessage(boolean showChargingStatus, String chargingStatusMessage) { + mChargingStatusTextView.setText(chargingStatusMessage); + mChargingStatusTextView.setVisibility(showChargingStatus ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public Animator onBeforeExitLowLight() { + mAnimationOut = mAnimationProvider.provideAnimationOut(mTextClock, mChargingStatusTextView); + mAnimationOut.start(); + + // Return the animator so that the transition coordinator waits for the low light exit + // animations to finish before entering low light, as otherwise the default DreamActivity + // animation plays immediately and there's no time for this animation to play. + return mAnimationOut; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java index 057ddcd54e68..8bfd2545ff2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java @@ -21,7 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent; import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize; -import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; +import static com.android.systemui.recents.LauncherProxyService.LauncherProxyListener; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; import static org.mockito.ArgumentMatchers.any; @@ -53,7 +53,7 @@ import androidx.test.filters.SmallTest; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.settings.SecureSettings; @@ -80,13 +80,13 @@ public class MagnificationTest extends SysuiTestCase { @Mock private IMagnificationConnectionCallback mConnectionCallback; @Mock - private OverviewProxyService mOverviewProxyService; + private LauncherProxyService mLauncherProxyService; @Mock private SecureSettings mSecureSettings; private CommandQueue mCommandQueue; private MagnificationImpl mMagnification; - private OverviewProxyListener mOverviewProxyListener; + private LauncherProxyListener mLauncherProxyListener; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Mock @@ -130,7 +130,7 @@ public class MagnificationTest extends SysuiTestCase { mMagnification = new MagnificationImpl(getContext(), getContext().getMainThreadHandler(), mContext.getMainExecutor(), mCommandQueue, mModeSwitchesController, - mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker, + mSysUiState, mLauncherProxyService, mSecureSettings, mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager, getContext().getSystemService(AccessibilityManager.class), mViewCaptureAwareWindowManager); @@ -140,10 +140,10 @@ public class MagnificationTest extends SysuiTestCase { mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController); mMagnification.start(); - final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor = - ArgumentCaptor.forClass(OverviewProxyListener.class); - verify(mOverviewProxyService).addCallback(listenerArgumentCaptor.capture()); - mOverviewProxyListener = listenerArgumentCaptor.getValue(); + final ArgumentCaptor<LauncherProxyListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(LauncherProxyListener.class); + verify(mLauncherProxyService).addCallback(listenerArgumentCaptor.capture()); + mLauncherProxyListener = listenerArgumentCaptor.getValue(); } @Test @@ -336,7 +336,7 @@ public class MagnificationTest extends SysuiTestCase { @Test public void overviewProxyIsConnected_noController_resetFlag() { - mOverviewProxyListener.onConnectionChanged(true); + mLauncherProxyListener.onConnectionChanged(true); verify(mSysUiState).setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false); verify(mSysUiState).commitUpdate(mContext.getDisplayId()); @@ -349,7 +349,7 @@ public class MagnificationTest extends SysuiTestCase { mContext.getSystemService(DisplayManager.class), mController); mMagnification.mWindowMagnificationControllerSupplier.get(TEST_DISPLAY); - mOverviewProxyListener.onConnectionChanged(true); + mLauncherProxyListener.onConnectionChanged(true); verify(mController).updateSysUIStateFlag(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt new file mode 100644 index 000000000000..43ee388e44a7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt @@ -0,0 +1,110 @@ +/* + * 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.lowlightclock + +import android.hardware.Sensor +import android.hardware.SensorEventListener +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.sensors.AsyncSensorManager +import java.util.Optional +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class AmbientLightModeMonitorTest : SysuiTestCase() { + @Mock private lateinit var sensorManager: AsyncSensorManager + @Mock private lateinit var sensor: Sensor + @Mock private lateinit var algorithm: AmbientLightModeMonitor.DebounceAlgorithm + + private lateinit var ambientLightModeMonitor: AmbientLightModeMonitor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + ambientLightModeMonitor = + AmbientLightModeMonitor(Optional.of(algorithm), sensorManager, Optional.of(sensor)) + } + + @Test + fun shouldRegisterSensorEventListenerOnStart() { + val callback = mock(AmbientLightModeMonitor.Callback::class.java) + ambientLightModeMonitor.start(callback) + + verify(sensorManager).registerListener(any(), eq(sensor), anyInt()) + } + + @Test + fun shouldUnregisterSensorEventListenerOnStop() { + val callback = mock(AmbientLightModeMonitor.Callback::class.java) + ambientLightModeMonitor.start(callback) + + val sensorEventListener = captureSensorEventListener() + + ambientLightModeMonitor.stop() + + verify(sensorManager).unregisterListener(eq(sensorEventListener)) + } + + @Test + fun shouldStartDebounceAlgorithmOnStart() { + val callback = mock(AmbientLightModeMonitor.Callback::class.java) + ambientLightModeMonitor.start(callback) + + verify(algorithm).start(eq(callback)) + } + + @Test + fun shouldStopDebounceAlgorithmOnStop() { + val callback = mock(AmbientLightModeMonitor.Callback::class.java) + ambientLightModeMonitor.start(callback) + ambientLightModeMonitor.stop() + + verify(algorithm).stop() + } + + @Test + fun shouldNotRegisterForSensorUpdatesIfSensorNotAvailable() { + val ambientLightModeMonitor = + AmbientLightModeMonitor(Optional.of(algorithm), sensorManager, Optional.empty()) + + val callback = mock(AmbientLightModeMonitor.Callback::class.java) + ambientLightModeMonitor.start(callback) + + verify(sensorManager, never()).registerListener(any(), any(Sensor::class.java), anyInt()) + } + + // Captures [SensorEventListener], assuming it has been registered with [sensorManager]. + private fun captureSensorEventListener(): SensorEventListener { + val captor = ArgumentCaptor.forClass(SensorEventListener::class.java) + verify(sensorManager).registerListener(captor.capture(), any(), anyInt()) + return captor.value + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ChargingStatusProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ChargingStatusProviderTest.java new file mode 100644 index 000000000000..2c8c1e1e70b1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ChargingStatusProviderTest.java @@ -0,0 +1,226 @@ +/* + * 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.lowlightclock; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; +import android.os.BatteryManager; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.internal.app.IBatteryStats; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.fuelgauge.BatteryStatus; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ChargingStatusProviderTest extends SysuiTestCase { + @Mock + private Resources mResources; + @Mock + private IBatteryStats mBatteryInfo; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private ChargingStatusProvider.Callback mCallback; + + private ChargingStatusProvider mProvider; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mProvider = new ChargingStatusProvider( + mContext, mResources, mBatteryInfo, mKeyguardUpdateMonitor); + } + + @Test + public void testStartUsingReportsStatusToCallback() { + mProvider.startUsing(mCallback); + verify(mCallback).onChargingStatusChanged(false, null); + } + + @Test + public void testStartUsingRegistersCallbackWithKeyguardUpdateMonitor() { + mProvider.startUsing(mCallback); + verify(mKeyguardUpdateMonitor).registerCallback(any()); + } + + @Test + public void testCallbackNotCalledAfterStopUsing() { + mProvider.startUsing(mCallback); + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + mProvider.stopUsing(); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getChargingBattery()); + verify(mCallback, never()).onChargingStatusChanged(eq(true), any()); + } + + @Test + public void testKeyguardUpdateMonitorCallbackRemovedAfterStopUsing() { + mProvider.startUsing(mCallback); + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + mProvider.stopUsing(); + verify(mKeyguardUpdateMonitor) + .removeCallback(keyguardUpdateMonitorCallbackArgumentCaptor.getValue()); + } + + @Test + public void testChargingStatusReportsHideWhenNotPluggedIn() { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + mProvider.startUsing(mCallback); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getUnpluggedBattery()); + // Once for init() and once for the status change. + verify(mCallback, times(2)).onChargingStatusChanged(false, null); + } + + @Test + public void testChargingStatusReportsShowWhenBatteryOverheated() { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + mProvider.startUsing(mCallback); + verify(mCallback).onChargingStatusChanged(false, null); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getBatteryDefender()); + verify(mCallback).onChargingStatusChanged(eq(true), any()); + } + + @Test + public void testChargingStatusReportsShowWhenPluggedIn() { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + mProvider.startUsing(mCallback); + verify(mCallback).onChargingStatusChanged(false, null); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getChargingBattery()); + verify(mCallback).onChargingStatusChanged(eq(true), any()); + } + + @Test + public void testChargingStatusReportsChargingLimitedWhenOverheated() { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + mProvider.startUsing(mCallback); + verify(mCallback).onChargingStatusChanged(false, null); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getBatteryDefender()); + verify(mResources).getString(eq(R.string.keyguard_plugged_in_charging_limited), any()); + } + + @Test + public void testChargingStatusReportsChargedWhenCharged() { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + mProvider.startUsing(mCallback); + verify(mCallback).onChargingStatusChanged(false, null); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getChargedBattery()); + verify(mResources).getString(R.string.keyguard_charged); + } + + @Test + public void testChargingStatusReportsPluggedInWhenDockedAndChargingTimeUnknown() throws + RemoteException { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + mProvider.startUsing(mCallback); + verify(mCallback).onChargingStatusChanged(false, null); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + when(mBatteryInfo.computeChargeTimeRemaining()).thenReturn(-1L); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getChargingBattery()); + verify(mResources).getString( + eq(R.string.keyguard_plugged_in_dock), any()); + } + + @Test + public void testChargingStatusReportsTimeRemainingWhenDockedAndCharging() throws + RemoteException { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + mProvider.startUsing(mCallback); + verify(mCallback).onChargingStatusChanged(false, null); + verify(mKeyguardUpdateMonitor) + .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + when(mBatteryInfo.computeChargeTimeRemaining()).thenReturn(1L); + keyguardUpdateMonitorCallbackArgumentCaptor.getValue() + .onRefreshBatteryInfo(getChargingBattery()); + verify(mResources).getString( + eq(R.string.keyguard_indication_charging_time_dock), any(), any()); + } + + private BatteryStatus getUnpluggedBattery() { + return new BatteryStatus(BatteryManager.BATTERY_STATUS_NOT_CHARGING, + 80, BatteryManager.BATTERY_PLUGGED_ANY, BatteryManager.BATTERY_HEALTH_GOOD, + 0, true); + } + + private BatteryStatus getChargingBattery() { + return new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING, + 80, BatteryManager.BATTERY_PLUGGED_DOCK, + BatteryManager.BATTERY_HEALTH_GOOD, 0, true); + } + + private BatteryStatus getChargedBattery() { + return new BatteryStatus(BatteryManager.BATTERY_STATUS_FULL, + 100, BatteryManager.BATTERY_PLUGGED_DOCK, + BatteryManager.BATTERY_HEALTH_GOOD, 0, true); + } + + private BatteryStatus getBatteryDefender() { + return new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING, + 80, BatteryManager.BATTERY_PLUGGED_DOCK, + BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0, true); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/DirectBootConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/DirectBootConditionTest.kt new file mode 100644 index 000000000000..173f243cb2b0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/DirectBootConditionTest.kt @@ -0,0 +1,102 @@ +/* + * 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.lowlightclock + +import android.content.Intent +import android.os.UserManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.condition.Condition +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class DirectBootConditionTest : SysuiTestCase() { + @Mock private lateinit var userManager: UserManager + @Mock private lateinit var callback: Condition.Callback + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun receiverRegisteredOnStart() = runTest { + val condition = buildCondition(this) + // No receivers are registered yet + assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0) + condition.addCallback(callback) + advanceUntilIdle() + // Receiver is registered after a callback is added + assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1) + condition.removeCallback(callback) + } + + @Test + fun unregisterReceiverOnStop() = runTest { + val condition = buildCondition(this) + + condition.addCallback(callback) + advanceUntilIdle() + + assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1) + + condition.removeCallback(callback) + advanceUntilIdle() + + // Receiver is unregistered when nothing is listening to the condition + assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0) + } + + @Test + fun callbackTriggeredWhenUserUnlocked() = runTest { + val condition = buildCondition(this) + + setUserUnlocked(false) + condition.addCallback(callback) + advanceUntilIdle() + + assertThat(condition.isConditionMet).isTrue() + + setUserUnlocked(true) + advanceUntilIdle() + + assertThat(condition.isConditionMet).isFalse() + condition.removeCallback(callback) + } + + private fun buildCondition(scope: CoroutineScope): DirectBootCondition { + return DirectBootCondition(fakeBroadcastDispatcher, userManager, scope) + } + + private fun setUserUnlocked(unlocked: Boolean) { + whenever(userManager.isUserUnlocked).thenReturn(unlocked) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_USER_UNLOCKED), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java new file mode 100644 index 000000000000..7297e0f3bff5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java @@ -0,0 +1,110 @@ +/* + * 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.lowlightclock; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.statusbar.commandline.Command; +import com.android.systemui.statusbar.commandline.CommandRegistry; + +import kotlin.jvm.functions.Function0; + +import kotlinx.coroutines.CoroutineScope; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.io.PrintWriter; +import java.util.Arrays; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ForceLowLightConditionTest extends SysuiTestCase { + @Mock + private CommandRegistry mCommandRegistry; + + @Mock + private Condition.Callback mCallback; + + @Mock + private PrintWriter mPrintWriter; + + @Mock + CoroutineScope mScope; + + private ForceLowLightCondition mCondition; + private Command mCommand; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mCondition = new ForceLowLightCondition(mScope, mCommandRegistry); + mCondition.addCallback(mCallback); + ArgumentCaptor<Function0<Command>> commandCaptor = + ArgumentCaptor.forClass(Function0.class); + verify(mCommandRegistry).registerCommand(eq(ForceLowLightCondition.COMMAND_ROOT), + commandCaptor.capture()); + mCommand = commandCaptor.getValue().invoke(); + } + + @Test + public void testEnableLowLight() { + mCommand.execute(mPrintWriter, + Arrays.asList(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT)); + verify(mCallback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionSet()).isTrue(); + assertThat(mCondition.isConditionMet()).isTrue(); + } + + @Test + public void testDisableLowLight() { + mCommand.execute(mPrintWriter, + Arrays.asList(ForceLowLightCondition.COMMAND_DISABLE_LOW_LIGHT)); + verify(mCallback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionSet()).isTrue(); + assertThat(mCondition.isConditionMet()).isFalse(); + } + + @Test + public void testClearEnableLowLight() { + mCommand.execute(mPrintWriter, + Arrays.asList(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT)); + verify(mCallback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionSet()).isTrue(); + assertThat(mCondition.isConditionMet()).isTrue(); + Mockito.clearInvocations(mCallback); + mCommand.execute(mPrintWriter, + Arrays.asList(ForceLowLightCondition.COMMAND_CLEAR_LOW_LIGHT)); + verify(mCallback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionSet()).isFalse(); + assertThat(mCondition.isConditionMet()).isFalse(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockAnimationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockAnimationProviderTest.kt new file mode 100644 index 000000000000..663880f098cd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockAnimationProviderTest.kt @@ -0,0 +1,75 @@ +/* + * 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.lowlightclock + +import android.animation.Animator +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +class LowLightClockAnimationProviderTest : SysuiTestCase() { + + private val underTest by lazy { + LowLightClockAnimationProvider( + Y_TRANSLATION_ANIMATION_OFFSET, + Y_TRANSLATION_ANIMATION_DURATION_MILLIS, + ALPHA_ANIMATION_IN_START_DELAY_MILLIS, + ALPHA_ANIMATION_DURATION_MILLIS, + ) + } + + @Test + fun animationOutEndsImmediatelyIfViewIsNull() { + val animator = underTest.provideAnimationOut(null, null) + + val listener = mock<Animator.AnimatorListener>() + animator.addListener(listener) + + animator.start() + verify(listener).onAnimationStart(any(), eq(false)) + verify(listener).onAnimationEnd(any(), eq(false)) + } + + @Test + fun animationInEndsImmediatelyIfViewIsNull() { + val animator = underTest.provideAnimationIn(null, null) + + val listener = mock<Animator.AnimatorListener>() + animator.addListener(listener) + + animator.start() + verify(listener).onAnimationStart(any(), eq(false)) + verify(listener).onAnimationEnd(any(), eq(false)) + } + + private companion object { + const val Y_TRANSLATION_ANIMATION_OFFSET = 100 + const val Y_TRANSLATION_ANIMATION_DURATION_MILLIS = 100L + const val ALPHA_ANIMATION_IN_START_DELAY_MILLIS = 200L + const val ALPHA_ANIMATION_DURATION_MILLIS = 300L + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockDreamServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockDreamServiceTest.java new file mode 100644 index 000000000000..22a13cc41425 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockDreamServiceTest.java @@ -0,0 +1,160 @@ +/* + * 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.lowlightclock; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.animation.Animator; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.dream.lowlight.LowLightTransitionCoordinator; +import com.android.systemui.SysuiTestCase; + +import com.google.android.systemui.lowlightclock.LowLightClockDreamService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class LowLightClockDreamServiceTest extends SysuiTestCase { + @Mock + private ChargingStatusProvider mChargingStatusProvider; + @Mock + private LowLightDisplayController mDisplayController; + @Mock + private LowLightClockAnimationProvider mAnimationProvider; + @Mock + private LowLightTransitionCoordinator mLowLightTransitionCoordinator; + @Mock + Animator mAnimationInAnimator; + @Mock + Animator mAnimationOutAnimator; + + private LowLightClockDreamService mService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mService = new LowLightClockDreamService( + mChargingStatusProvider, + mAnimationProvider, + mLowLightTransitionCoordinator, + Optional.of(() -> mDisplayController)); + + when(mAnimationProvider.provideAnimationIn(any(), any())).thenReturn(mAnimationInAnimator); + when(mAnimationProvider.provideAnimationOut(any())).thenReturn( + mAnimationOutAnimator); + } + + @Test + public void testSetDbmStateWhenSupported() throws RemoteException { + when(mDisplayController.isDisplayBrightnessModeSupported()).thenReturn(true); + + mService.onDreamingStarted(); + + verify(mDisplayController).setDisplayBrightnessModeEnabled(true); + } + + @Test + public void testNotSetDbmStateWhenNotSupported() throws RemoteException { + when(mDisplayController.isDisplayBrightnessModeSupported()).thenReturn(false); + + mService.onDreamingStarted(); + + verify(mDisplayController, never()).setDisplayBrightnessModeEnabled(anyBoolean()); + } + + @Test + public void testClearDbmState() throws RemoteException { + when(mDisplayController.isDisplayBrightnessModeSupported()).thenReturn(true); + + mService.onDreamingStarted(); + clearInvocations(mDisplayController); + + mService.onDreamingStopped(); + + verify(mDisplayController).setDisplayBrightnessModeEnabled(false); + } + + @Test + public void testAnimationsStartedOnDreamingStarted() { + mService.onDreamingStarted(); + + // Entry animation started. + verify(mAnimationInAnimator).start(); + } + + @Test + public void testAnimationsStartedOnWakeUp() { + // Start dreaming then wake up. + mService.onDreamingStarted(); + mService.onWakeUp(); + + // Entry animation started. + verify(mAnimationInAnimator).cancel(); + + // Exit animation started. + verify(mAnimationOutAnimator).start(); + } + + @Test + public void testAnimationsStartedBeforeExitingLowLight() { + mService.onBeforeExitLowLight(); + + // Exit animation started. + verify(mAnimationOutAnimator).start(); + } + + @Test + public void testWakeUpAnimationCancelledOnDetach() { + mService.onWakeUp(); + + // Exit animation started. + verify(mAnimationOutAnimator).start(); + + mService.onDetachedFromWindow(); + + verify(mAnimationOutAnimator).cancel(); + } + + @Test + public void testExitLowLightAnimationCancelledOnDetach() { + mService.onBeforeExitLowLight(); + + // Exit animation started. + verify(mAnimationOutAnimator).start(); + + mService.onDetachedFromWindow(); + + verify(mAnimationOutAnimator).cancel(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java new file mode 100644 index 000000000000..2c216244985e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java @@ -0,0 +1,143 @@ +/* + * 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.lowlightclock; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.SysuiTestCase; + +import kotlinx.coroutines.CoroutineScope; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class LowLightConditionTest extends SysuiTestCase { + @Mock + private AmbientLightModeMonitor mAmbientLightModeMonitor; + @Mock + private UiEventLogger mUiEventLogger; + @Mock + CoroutineScope mScope; + private LowLightCondition mCondition; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mCondition = new LowLightCondition(mScope, mAmbientLightModeMonitor, mUiEventLogger); + mCondition.start(); + } + + @Test + public void testLowLightFalse() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT); + assertThat(mCondition.isConditionMet()).isFalse(); + } + + @Test + public void testLowLightTrue() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + assertThat(mCondition.isConditionMet()).isTrue(); + } + + @Test + public void testUndecidedLowLightStateIgnored() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + assertThat(mCondition.isConditionMet()).isTrue(); + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED); + assertThat(mCondition.isConditionMet()).isTrue(); + } + + @Test + public void testLowLightChange() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT); + assertThat(mCondition.isConditionMet()).isFalse(); + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + assertThat(mCondition.isConditionMet()).isTrue(); + } + + @Test + public void testResetIsConditionMetUponStop() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + assertThat(mCondition.isConditionMet()).isTrue(); + + mCondition.stop(); + assertThat(mCondition.isConditionMet()).isFalse(); + } + + @Test + public void testLoggingAmbientLightNotLowToLow() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + // Only logged once. + verify(mUiEventLogger, times(1)).log(any()); + // Logged with the correct state. + verify(mUiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_DARK); + } + + @Test + public void testLoggingAmbientLightLowToLow() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + reset(mUiEventLogger); + + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + // Doesn't log. + verify(mUiEventLogger, never()).log(any()); + } + + @Test + public void testLoggingAmbientLightNotLowToNotLow() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT); + // Doesn't log. + verify(mUiEventLogger, never()).log(any()); + } + + @Test + public void testLoggingAmbientLightLowToNotLow() { + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); + reset(mUiEventLogger); + + changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT); + // Only logged once. + verify(mUiEventLogger).log(any()); + // Logged with the correct state. + verify(mUiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT); + } + + private void changeLowLightMode(int mode) { + ArgumentCaptor<AmbientLightModeMonitor.Callback> ambientLightCallbackCaptor = + ArgumentCaptor.forClass(AmbientLightModeMonitor.Callback.class); + verify(mAmbientLightModeMonitor).start(ambientLightCallbackCaptor.capture()); + ambientLightCallbackCaptor.getValue().onChange(mode); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java new file mode 100644 index 000000000000..69485e848a6a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java @@ -0,0 +1,183 @@ +/* + * 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.lowlightclock; + +import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT; +import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR; +import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.dream.lowlight.LowLightDreamManager; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.shared.condition.Monitor; + +import dagger.Lazy; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Set; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class LowLightMonitorTest extends SysuiTestCase { + + @Mock + private Lazy<LowLightDreamManager> mLowLightDreamManagerLazy; + @Mock + private LowLightDreamManager mLowLightDreamManager; + @Mock + private Monitor mMonitor; + @Mock + private ScreenLifecycle mScreenLifecycle; + @Mock + private LowLightLogger mLogger; + + private LowLightMonitor mLowLightMonitor; + + @Mock + Lazy<Set<Condition>> mLazyConditions; + + @Mock + private PackageManager mPackageManager; + + @Mock + private ComponentName mDreamComponent; + + Condition mCondition = mock(Condition.class); + Set<Condition> mConditionSet = Set.of(mCondition); + + @Captor + ArgumentCaptor<Monitor.Subscription> mPreconditionsSubscriptionCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mLowLightDreamManagerLazy.get()).thenReturn(mLowLightDreamManager); + when(mLazyConditions.get()).thenReturn(mConditionSet); + mLowLightMonitor = new LowLightMonitor(mLowLightDreamManagerLazy, + mMonitor, mLazyConditions, mScreenLifecycle, mLogger, mDreamComponent, + mPackageManager); + } + + @Test + public void testSetAmbientLowLightWhenInLowLight() { + mLowLightMonitor.onConditionsChanged(true); + // Verify setting low light when condition is true + verify(mLowLightDreamManager).setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); + } + + @Test + public void testExitAmbientLowLightWhenNotInLowLight() { + mLowLightMonitor.onConditionsChanged(true); + mLowLightMonitor.onConditionsChanged(false); + // Verify ambient light toggles back to light mode regular + verify(mLowLightDreamManager).setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR); + } + + @Test + public void testStartMonitorLowLightConditionsWhenScreenTurnsOn() { + mLowLightMonitor.onScreenTurnedOn(); + + // Verify subscribing to low light conditions monitor when screen turns on. + verify(mMonitor).addSubscription(any()); + } + + @Test + public void testStopMonitorLowLightConditionsWhenScreenTurnsOff() { + final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class); + when(mMonitor.addSubscription(any())).thenReturn(token); + mLowLightMonitor.onScreenTurnedOn(); + + // Verify removing subscription when screen turns off. + mLowLightMonitor.onScreenTurnedOff(); + verify(mMonitor).removeSubscription(token); + } + + @Test + public void testSubscribeToLowLightConditionsOnlyOnceWhenScreenTurnsOn() { + final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class); + when(mMonitor.addSubscription(any())).thenReturn(token); + + mLowLightMonitor.onScreenTurnedOn(); + mLowLightMonitor.onScreenTurnedOn(); + // Verify subscription is only added once. + verify(mMonitor, times(1)).addSubscription(any()); + } + + @Test + public void testSubscribedToExpectedConditions() { + final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class); + when(mMonitor.addSubscription(any())).thenReturn(token); + + mLowLightMonitor.onScreenTurnedOn(); + mLowLightMonitor.onScreenTurnedOn(); + Set<Condition> conditions = captureConditions(); + // Verify Monitor is subscribed to the expected conditions + assertThat(conditions).isEqualTo(mConditionSet); + } + + @Test + public void testNotUnsubscribeIfNotSubscribedWhenScreenTurnsOff() { + mLowLightMonitor.onScreenTurnedOff(); + + // Verify doesn't remove subscription since there is none. + verify(mMonitor, never()).removeSubscription(any()); + } + + @Test + public void testSubscribeIfScreenIsOnWhenStarting() { + when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON); + mLowLightMonitor.start(); + // Verify to add subscription on start if the screen state is on + verify(mMonitor, times(1)).addSubscription(any()); + } + + @Test + public void testNoSubscribeIfDreamNotPresent() { + LowLightMonitor lowLightMonitor = new LowLightMonitor(mLowLightDreamManagerLazy, + mMonitor, mLazyConditions, mScreenLifecycle, mLogger, null, mPackageManager); + when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON); + lowLightMonitor.start(); + verify(mScreenLifecycle, never()).addObserver(any()); + } + + private Set<Condition> captureConditions() { + verify(mMonitor).addSubscription(mPreconditionsSubscriptionCaptor.capture()); + return mPreconditionsSubscriptionCaptor.getValue().getConditions(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java new file mode 100644 index 000000000000..366c071fb93f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java @@ -0,0 +1,108 @@ +/* + * 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.lowlightclock; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.UserHandle; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.settings.SecureSettings; + +import kotlinx.coroutines.CoroutineScope; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ScreenSaverEnabledConditionTest extends SysuiTestCase { + @Mock + private Resources mResources; + @Mock + private SecureSettings mSecureSettings; + @Mock + CoroutineScope mScope; + @Captor + private ArgumentCaptor<ContentObserver> mSettingsObserverCaptor; + private ScreenSaverEnabledCondition mCondition; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + // Default dreams to enabled by default + doReturn(true).when(mResources).getBoolean( + com.android.internal.R.bool.config_dreamsEnabledByDefault); + + mCondition = new ScreenSaverEnabledCondition(mScope, mResources, mSecureSettings); + } + + @Test + public void testScreenSaverInitiallyEnabled() { + setScreenSaverEnabled(true); + mCondition.start(); + assertThat(mCondition.isConditionMet()).isTrue(); + } + + @Test + public void testScreenSaverInitiallyDisabled() { + setScreenSaverEnabled(false); + mCondition.start(); + assertThat(mCondition.isConditionMet()).isFalse(); + } + + @Test + public void testScreenSaverStateChanges() { + setScreenSaverEnabled(false); + mCondition.start(); + assertThat(mCondition.isConditionMet()).isFalse(); + + setScreenSaverEnabled(true); + final ContentObserver observer = captureSettingsObserver(); + observer.onChange(/* selfChange= */ false); + assertThat(mCondition.isConditionMet()).isTrue(); + } + + private void setScreenSaverEnabled(boolean enabled) { + when(mSecureSettings.getIntForUser(eq(Settings.Secure.SCREENSAVER_ENABLED), anyInt(), + eq(UserHandle.USER_CURRENT))).thenReturn(enabled ? 1 : 0); + } + + private ContentObserver captureSettingsObserver() { + verify(mSecureSettings).registerContentObserverForUserSync( + eq(Settings.Secure.SCREENSAVER_ENABLED), + mSettingsObserverCaptor.capture(), eq(UserHandle.USER_CURRENT)); + return mSettingsObserverCaptor.getValue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java index d59a404b15bb..0924df2538e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java @@ -52,7 +52,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.views.NavigationBar; -import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -109,7 +109,7 @@ public class NavigationBarControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mNavigationBarController = spy( new NavigationBarControllerImpl(mContext, - mock(OverviewProxyService.class), + mock(LauncherProxyService.class), mock(NavigationModeController.class), mock(SysUiState.class), mCommandQueue, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt index a192446e535b..50b8f37f8d25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.dialog import android.content.Intent import android.os.Handler import android.os.fakeExecutorHandler +import android.platform.test.annotations.EnableFlags import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.telephonyManager @@ -38,8 +39,7 @@ import com.android.internal.logging.UiEventLogger import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.DialogTransitionAnimator -import com.android.systemui.flags.setFlagValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.Kosmos import com.android.systemui.res.R import com.android.systemui.statusbar.policy.KeyguardStateController @@ -62,6 +62,8 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) +@EnableSceneContainer +@EnableFlags(Flags.FLAG_QS_TILE_DETAILED_VIEW, Flags.FLAG_DUAL_SHADE) @UiThreadTest class InternetDetailsContentManagerTest : SysuiTestCase() { private val kosmos = Kosmos() @@ -74,11 +76,8 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { private val internetDetailsContentController: InternetDetailsContentController = mock<InternetDetailsContentController>() private val keyguard: KeyguardStateController = mock<KeyguardStateController>() - private val dialogTransitionAnimator: DialogTransitionAnimator = - mock<DialogTransitionAnimator>() private val bgExecutor = FakeExecutor(FakeSystemClock()) private lateinit var internetDetailsContentManager: InternetDetailsContentManager - private var subTitle: View? = null private var ethernet: LinearLayout? = null private var mobileDataLayout: LinearLayout? = null private var mobileToggleSwitch: Switch? = null @@ -96,8 +95,6 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { @Before fun setUp() { - // TODO: b/377388104 enable this flag after integrating with details view. - mSetFlagsRule.setFlagValue(Flags.FLAG_QS_TILE_DETAILED_VIEW, false) whenever(telephonyManager.createForSubscriptionId(ArgumentMatchers.anyInt())) .thenReturn(telephonyManager) whenever(internetWifiEntry.title).thenReturn(WIFI_TITLE) @@ -133,9 +130,7 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { canConfigWifi = true, coroutineScope = scope, context = mContext, - internetDialog = null, uiEventLogger = mock<UiEventLogger>(), - dialogTransitionAnimator = dialogTransitionAnimator, handler = handler, backgroundExecutor = bgExecutor, keyguard = keyguard, @@ -146,7 +141,6 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { internetDetailsContentManager.connectedWifiEntry = internetWifiEntry internetDetailsContentManager.wifiEntriesCount = wifiEntries.size - subTitle = contentView.requireViewById(R.id.internet_dialog_subtitle) ethernet = contentView.requireViewById(R.id.ethernet_layout) mobileDataLayout = contentView.requireViewById(R.id.mobile_network_layout) mobileToggleSwitch = contentView.requireViewById(R.id.mobile_toggle) @@ -185,32 +179,6 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { } @Test - fun updateContent_withApmOn_internetDialogSubTitleGone() { - whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true) - internetDetailsContentManager.updateContent(true) - bgExecutor.runAllReady() - - internetDetailsContentManager.internetContentData.observe( - internetDetailsContentManager.lifecycleOwner!! - ) { - assertThat(subTitle!!.visibility).isEqualTo(View.VISIBLE) - } - } - - @Test - fun updateContent_withApmOff_internetDialogSubTitleVisible() { - whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false) - internetDetailsContentManager.updateContent(true) - bgExecutor.runAllReady() - - internetDetailsContentManager.internetContentData.observe( - internetDetailsContentManager.lifecycleOwner!! - ) { - assertThat(subTitle!!.visibility).isEqualTo(View.VISIBLE) - } - } - - @Test fun updateContent_apmOffAndHasEthernet_showEthernet() { whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false) whenever(internetDetailsContentController.hasEthernet()).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt index 4e1ccfb07220..69b762b470b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt @@ -40,11 +40,11 @@ import com.android.systemui.model.sceneContainerPlugin import com.android.systemui.navigationbar.NavigationBarController import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.process.ProcessWrapper -import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP +import com.android.systemui.recents.LauncherProxyService.ACTION_QUICKSTEP import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeViewController -import com.android.systemui.shared.recents.IOverviewProxy +import com.android.systemui.shared.recents.ILauncherProxy import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_ASLEEP import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE @@ -83,12 +83,12 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -class OverviewProxyServiceTest : SysuiTestCase() { +class LauncherProxyServiceTest : SysuiTestCase() { @Main private val executor: Executor = MoreExecutors.directExecutor() private val kosmos = testKosmos() - private lateinit var subject: OverviewProxyService + private lateinit var subject: LauncherProxyService @Mock private val dumpManager = DumpManager() @Mock private val processWrapper = ProcessWrapper() private val displayTracker = FakeDisplayTracker(mContext) @@ -97,10 +97,10 @@ class OverviewProxyServiceTest : SysuiTestCase() { private val wakefulnessLifecycle = WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager) - @Mock private lateinit var overviewProxy: IOverviewProxy.Stub + @Mock private lateinit var launcherProxy: ILauncherProxy.Stub @Mock private lateinit var packageManager: PackageManager - // The following mocks belong to not-yet-tested parts of OverviewProxyService. + // The following mocks belong to not-yet-tested parts of LauncherProxyService. @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var shellInterface: ShellInterface @Mock private lateinit var navBarController: NavigationBarController @@ -127,18 +127,18 @@ class OverviewProxyServiceTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) val serviceComponent = ComponentName("test_package", "service_provider") - context.addMockService(serviceComponent, overviewProxy) + context.addMockService(serviceComponent, launcherProxy) context.addMockServiceResolver( TestableContext.MockServiceResolver { if (it.action == ACTION_QUICKSTEP) serviceComponent else null } ) - whenever(overviewProxy.queryLocalInterface(ArgumentMatchers.anyString())) - .thenReturn(overviewProxy) - whenever(overviewProxy.asBinder()).thenReturn(overviewProxy) + whenever(launcherProxy.queryLocalInterface(ArgumentMatchers.anyString())) + .thenReturn(launcherProxy) + whenever(launcherProxy.asBinder()).thenReturn(launcherProxy) // packageManager.resolveServiceAsUser has to return non-null for - // OverviewProxyService#isEnabled to become true. + // LauncherProxyService#isEnabled to become true. context.setMockPackageManager(packageManager) whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt())) .thenReturn(mock(ResolveInfo::class.java)) @@ -147,7 +147,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { // return isSystemUser as true by default. `when`(processWrapper.isSystemUser).thenReturn(true) - subject = createOverviewProxyService(context) + subject = createLauncherProxyService(context) } @After @@ -159,11 +159,11 @@ class OverviewProxyServiceTest : SysuiTestCase() { fun wakefulnessLifecycle_dispatchFinishedWakingUpSetsSysUIflagToAWAKE() { // WakefulnessLifecycle is initialized to AWAKE initially, and won't emit a noop. wakefulnessLifecycle.dispatchFinishedGoingToSleep() - clearInvocations(overviewProxy) + clearInvocations(launcherProxy) wakefulnessLifecycle.dispatchFinishedWakingUp() - verify(overviewProxy) + verify(launcherProxy) .onSystemUiStateChanged( longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE } ) @@ -173,7 +173,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { fun wakefulnessLifecycle_dispatchStartedWakingUpSetsSysUIflagToWAKING() { wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN) - verify(overviewProxy) + verify(launcherProxy) .onSystemUiStateChanged( longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING } ) @@ -183,7 +183,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { fun wakefulnessLifecycle_dispatchFinishedGoingToSleepSetsSysUIflagToASLEEP() { wakefulnessLifecycle.dispatchFinishedGoingToSleep() - verify(overviewProxy) + verify(launcherProxy) .onSystemUiStateChanged( longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP } ) @@ -195,56 +195,56 @@ class OverviewProxyServiceTest : SysuiTestCase() { PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON ) - verify(overviewProxy) + verify(launcherProxy) .onSystemUiStateChanged( longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP } ) } @Test - fun connectToOverviewService_primaryUserNoVisibleBgUsersSupported_expectBindService() { + fun connectToLauncherService_primaryUserNoVisibleBgUsersSupported_expectBindService() { `when`(processWrapper.isSystemUser).thenReturn(true) `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) + val ops = createLauncherProxyService(spyContext) ops.startConnectionToCurrentUser() verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any()) } @Test - fun connectToOverviewService_nonPrimaryUserNoVisibleBgUsersSupported_expectNoBindService() { + fun connectToLauncherService_nonPrimaryUserNoVisibleBgUsersSupported_expectNoBindService() { `when`(processWrapper.isSystemUser).thenReturn(false) `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) + val ops = createLauncherProxyService(spyContext) ops.startConnectionToCurrentUser() verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) } @Test - fun connectToOverviewService_nonPrimaryBgUserVisibleBgUsersSupported_expectBindService() { + fun connectToLauncherService_nonPrimaryBgUserVisibleBgUsersSupported_expectBindService() { `when`(processWrapper.isSystemUser).thenReturn(false) `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) `when`(userManager.isUserForeground()).thenReturn(false) val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) + val ops = createLauncherProxyService(spyContext) ops.startConnectionToCurrentUser() verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any()) } @Test - fun connectToOverviewService_nonPrimaryFgUserVisibleBgUsersSupported_expectNoBindService() { + fun connectToLauncherService_nonPrimaryFgUserVisibleBgUsersSupported_expectNoBindService() { `when`(processWrapper.isSystemUser).thenReturn(false) `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) `when`(userManager.isUserForeground()).thenReturn(true) val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) + val ops = createLauncherProxyService(spyContext) ops.startConnectionToCurrentUser() verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) } - private fun createOverviewProxyService(ctx: Context): OverviewProxyService { - return OverviewProxyService( + private fun createLauncherProxyService(ctx: Context): LauncherProxyService { + return LauncherProxyService( ctx, executor, commandQueue, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index a04ca038021e..9abe9aa5e598 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -32,8 +32,8 @@ import com.android.systemui.fragments.FragmentService import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.plugins.qs.QS -import com.android.systemui.recents.OverviewProxyService -import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.recents.LauncherProxyService +import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -71,7 +71,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { private val view = mock<NotificationsQuickSettingsContainer>() private val navigationModeController = mock<NavigationModeController>() - private val overviewProxyService = mock<OverviewProxyService>() + private val mLauncherProxyService = mock<LauncherProxyService>() private val shadeHeaderController = mock<ShadeHeaderController>() private val shadeInteractor = mock<ShadeInteractor>() private val fragmentService = mock<FragmentService>() @@ -81,7 +81,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>() @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> - @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener> + @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<LauncherProxyListener> @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>> @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet> @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener> @@ -89,7 +89,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { lateinit var underTest: NotificationsQSContainerController private lateinit var navigationModeCallback: ModeChangedListener - private lateinit var taskbarVisibilityCallback: OverviewProxyListener + private lateinit var taskbarVisibilityCallback: LauncherProxyListener private lateinit var windowInsetsCallback: Consumer<WindowInsets> private lateinit var fakeSystemClock: FakeSystemClock private lateinit var delayableExecutor: FakeExecutor @@ -110,7 +110,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { NotificationsQSContainerController( view, navigationModeController, - overviewProxyService, + mLauncherProxyService, shadeHeaderController, shadeInteractor, fragmentService, @@ -127,7 +127,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET) whenever(navigationModeController.addListener(navigationModeCaptor.capture())) .thenReturn(GESTURES_NAVIGATION) - doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture()) + doNothing().`when`(mLauncherProxyService).addCallback(taskbarVisibilityCaptor.capture()) doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture()) doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture()) doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture()) @@ -402,7 +402,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { NotificationsQSContainerController( container, navigationModeController, - overviewProxyService, + mLauncherProxyService, shadeHeaderController, shadeInteractor, fragmentService, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index 24f8843e935d..4c12cc886e33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -32,8 +32,8 @@ import com.android.systemui.fragments.FragmentService import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.plugins.qs.QS -import com.android.systemui.recents.OverviewProxyService -import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.recents.LauncherProxyService +import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -70,7 +70,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { private val view = mock<NotificationsQuickSettingsContainer>() private val navigationModeController = mock<NavigationModeController>() - private val overviewProxyService = mock<OverviewProxyService>() + private val mLauncherProxyService = mock<LauncherProxyService>() private val shadeHeaderController = mock<ShadeHeaderController>() private val shadeInteractor = mock<ShadeInteractor>() private val fragmentService = mock<FragmentService>() @@ -80,7 +80,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>() @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> - @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener> + @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<LauncherProxyListener> @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>> @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet> @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener> @@ -88,7 +88,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { lateinit var underTest: NotificationsQSContainerController private lateinit var navigationModeCallback: ModeChangedListener - private lateinit var taskbarVisibilityCallback: OverviewProxyListener + private lateinit var taskbarVisibilityCallback: LauncherProxyListener private lateinit var windowInsetsCallback: Consumer<WindowInsets> private lateinit var fakeSystemClock: FakeSystemClock private lateinit var delayableExecutor: FakeExecutor @@ -110,7 +110,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { NotificationsQSContainerController( view, navigationModeController, - overviewProxyService, + mLauncherProxyService, shadeHeaderController, shadeInteractor, fragmentService, @@ -127,7 +127,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET) whenever(navigationModeController.addListener(navigationModeCaptor.capture())) .thenReturn(GESTURES_NAVIGATION) - doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture()) + doNothing().`when`(mLauncherProxyService).addCallback(taskbarVisibilityCaptor.capture()) doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture()) doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture()) doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture()) @@ -457,7 +457,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { NotificationsQSContainerController( container, navigationModeController, - overviewProxyService, + mLauncherProxyService, shadeHeaderController, shadeInteractor, fragmentService, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index a5cd81ff3116..e8ab76181af2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -296,7 +296,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { verify(clock).setTextAppearance(R.style.TextAppearance_QS_Status) verify(date).setTextAppearance(R.style.TextAppearance_QS_Status) - verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers) + verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt index 1eb88c5a5616..0457255fee4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt @@ -19,10 +19,12 @@ import android.app.Notification import android.app.Person import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper +import android.view.View.GONE import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflatePrivateSingleLineView @@ -90,6 +92,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { builder = notificationBuilder, systemUiContext = context, redactText = false, + summarization = null ) // WHEN: binds the viewHolder @@ -151,6 +154,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { builder = notificationBuilder, systemUiContext = context, redactText = false, + summarization = null ) // WHEN: binds the view SingleLineViewBinder.bind(viewModel, view) @@ -200,6 +204,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { builder = notificationBuilder, systemUiContext = context, redactText = false, + summarization = null ) // WHEN: binds the view with the view model SingleLineViewBinder.bind(viewModel, view) @@ -211,6 +216,70 @@ class SingleLineViewBinderTest : SysuiTestCase() { assertNull(viewModel.conversationData) } + @Test + @EnableFlags(AsyncHybridViewInflation.FLAG_NAME, android.app.Flags.FLAG_NM_SUMMARIZATION_UI, + android.app.Flags.FLAG_NM_SUMMARIZATION) + fun bindSummarizedGroupConversationSingleLineView() { + // GIVEN a row with a group conversation notification + val user = + Person.Builder() + .setName(USER_NAME) + .build() + val style = + Notification.MessagingStyle(user) + .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user) + .addMessage( + "How about lunch?", + System.currentTimeMillis(), + Person.Builder().setName("user2").build(), + ) + .setGroupConversation(true) + notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID) + val notification = notificationBuilder.build() + val row = helper.createRow(notification) + val rb = RankingBuilder(row.entry.ranking) + rb.setSummarization("summary!") + row.entry.ranking = rb.build() + + val view = + inflatePrivateSingleLineView( + isConversation = true, + reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE, + entry = row.entry, + context = context, + logger = mock(), + ) + as HybridConversationNotificationView + + val publicView = + inflatePublicSingleLineView( + isConversation = true, + reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, + entry = row.entry, + context = context, + logger = mock(), + ) + as HybridConversationNotificationView + assertNotNull(publicView) + + val viewModel = + SingleLineViewInflater.inflateSingleLineViewModel( + notification = notification, + messagingStyle = style, + builder = notificationBuilder, + systemUiContext = context, + redactText = false, + summarization = "summary" + ) + // WHEN: binds the view + SingleLineViewBinder.bind(viewModel, view) + + // THEN: the single-line conversation view should only include summarization content + assertEquals(viewModel.conversationData?.summarization, view.textView.text) + assertEquals("", view.conversationSenderNameView.text) + assertEquals(GONE, view.conversationSenderNameView.visibility) + } + private companion object { const val CHANNEL_ID = "CHANNEL_ID" const val CONTENT_TITLE = "A Cool New Feature" @@ -218,5 +287,6 @@ class SingleLineViewBinderTest : SysuiTestCase() { const val USER_NAME = "USER_NAME" const val MESSAGE_TEXT = "MESSAGE_TEXT" const val SHORTCUT_ID = "Shortcut" + const val SUMMARIZATION = "summarization" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt index ef70e277832e..13724a8b44da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt @@ -272,6 +272,35 @@ class SingleLineViewInflaterTest : SysuiTestCase() { } } + @Test + fun createViewModelForSummarizedConversationNotification() { + // Given: a non-group conversation notification + val notificationType = OneToOneConversation() + val notification = getNotification(notificationType) + + // When: inflate the SingleLineViewModel + val singleLineViewModel = notification.makeSingleLineViewModel(notificationType) + + // Then: the inflated SingleLineViewModel should be as expected + // titleText: Notification.ConversationTitle + // contentText: the last message text + // conversationSenderName: null, because it's not a group conversation + // conversationData.avatar: a single icon of the last sender + // summarizedText: the summary text from the ranking + assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText) + assertEquals(LAST_MESSAGE, singleLineViewModel.contentText) + assertNull( + singleLineViewModel.conversationData?.conversationSenderName, + "Sender name should be null for one-on-one conversation" + ) + assertTrue { + singleLineViewModel.conversationData + ?.avatar + ?.equalsTo(SingleIcon(firstSenderIcon.loadDrawable(context))) == true + } + assertEquals("summary", singleLineViewModel.conversationData?.summarization) + } + sealed class NotificationType(val largeIcon: Icon? = null) class NonMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon) @@ -380,7 +409,8 @@ class SingleLineViewInflaterTest : SysuiTestCase() { if (isConversation) messagingStyle else null, builder, context, - false + false, + "summary" ) } diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt index de4bbecaaf0e..42c509eeaa0b 100644 --- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt +++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt @@ -16,16 +16,19 @@ package android.hardware.input +import android.hardware.input.InputGestureData.Trigger +import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS +import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST +import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.InputManager.InputDeviceListener import android.view.InputDevice import android.view.KeyCharacterMap import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD import android.view.KeyEvent -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import org.mockito.ArgumentMatchers.anyInt import org.mockito.invocation.InvocationOnMock +import org.mockito.kotlin.any +import org.mockito.kotlin.mock class FakeInputManager { @@ -49,36 +52,79 @@ class FakeInputManager { ) private var inputDeviceListener: InputDeviceListener? = null + private val customInputGestures: MutableMap<Trigger, InputGestureData> = mutableMapOf() + var addCustomInputGestureErrorCode = CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS + + val inputManager: InputManager = mock { + on { getCustomInputGestures(any()) }.then { customInputGestures.values.toList() } + + on { addCustomInputGesture(any()) } + .then { + val inputGestureData = it.getArgument<InputGestureData>(0) + val trigger = inputGestureData.trigger + + if (customInputGestures.containsKey(trigger)) { + addCustomInputGestureErrorCode + } else { + customInputGestures[trigger] = inputGestureData + CUSTOM_INPUT_GESTURE_RESULT_SUCCESS + } + } + + on { removeCustomInputGesture(any()) } + .then { + val inputGestureData = it.getArgument<InputGestureData>(0) + val trigger = inputGestureData.trigger + + if (customInputGestures.containsKey(trigger)) { + customInputGestures.remove(trigger) + CUSTOM_INPUT_GESTURE_RESULT_SUCCESS + } else { + CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST + } + } + + on { removeAllCustomInputGestures(any()) }.then { customInputGestures.clear() } - val inputManager = - mock<InputManager> { - whenever(getInputDevice(anyInt())).thenAnswer { invocation -> + on { getInputGesture(any()) } + .then { + val trigger = it.getArgument<Trigger>(0) + customInputGestures[trigger] + } + + on { getInputDevice(anyInt()) } + .thenAnswer { invocation -> val deviceId = invocation.arguments[0] as Int return@thenAnswer devices[deviceId] } - whenever(inputDeviceIds).thenAnswer { + on { inputDeviceIds } + .thenAnswer { return@thenAnswer devices.keys.toIntArray() } - fun setDeviceEnabled(invocation: InvocationOnMock, enabled: Boolean) { - val deviceId = invocation.arguments[0] as Int - val device = devices[deviceId] ?: return - devices[deviceId] = device.copy(enabled = enabled) - } + fun setDeviceEnabled(invocation: InvocationOnMock, enabled: Boolean) { + val deviceId = invocation.arguments[0] as Int + val device = devices[deviceId] ?: return + devices[deviceId] = device.copy(enabled = enabled) + } - whenever(disableInputDevice(anyInt())).thenAnswer { invocation -> - setDeviceEnabled(invocation, enabled = false) - } - whenever(enableInputDevice(anyInt())).thenAnswer { invocation -> - setDeviceEnabled(invocation, enabled = true) - } - whenever(deviceHasKeys(any(), any())).thenAnswer { invocation -> + on { disableInputDevice(anyInt()) } + .thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = false) } + on { enableInputDevice(anyInt()) } + .thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = true) } + on { deviceHasKeys(any(), any()) } + .thenAnswer { invocation -> val deviceId = invocation.arguments[0] as Int val keyCodes = invocation.arguments[1] as IntArray val supportedKeyCodes = supportedKeyCodesByDeviceId[deviceId]!! return@thenAnswer keyCodes.map { supportedKeyCodes.contains(it) }.toBooleanArray() } - } + } + + fun resetCustomInputGestures() { + customInputGestures.clear() + addCustomInputGestureErrorCode = CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS + } fun addPhysicalKeyboardIfNotPresent(deviceId: Int, enabled: Boolean = true) { if (devices.containsKey(deviceId)) { @@ -97,7 +143,7 @@ class FakeInputManager { vendorId: Int = 0, productId: Int = 0, isFullKeyboard: Boolean = true, - enabled: Boolean = true + enabled: Boolean = true, ) { check(id > 0) { "Physical keyboard ids have to be > 0" } addKeyboard(id, vendorId, productId, isFullKeyboard, enabled) @@ -113,7 +159,7 @@ class FakeInputManager { vendorId: Int = 0, productId: Int = 0, isFullKeyboard: Boolean = true, - enabled: Boolean = true + enabled: Boolean = true, ) { val keyboardType = if (isFullKeyboard) InputDevice.KEYBOARD_TYPE_ALPHABETIC @@ -152,7 +198,7 @@ class FakeInputManager { id: Int = getId(), type: Int = keyboardType, sources: Int = getSources(), - enabled: Boolean = isEnabled + enabled: Boolean = isEnabled, ) = InputDevice.Builder() .setId(id) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt index 93e7f2e588b0..83f4e8f5aa49 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt @@ -25,7 +25,7 @@ import com.android.systemui.keyboard.data.repository.keyboardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope -import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.recents.LauncherProxyService import com.android.systemui.touchpad.data.repository.touchpadRepository import com.android.systemui.user.data.repository.userRepository import org.mockito.kotlin.mock @@ -43,12 +43,12 @@ var Kosmos.keyboardTouchpadEduInteractor by userRepository, ), tutorialRepository = tutorialSchedulerRepository, - overviewProxyService = mockOverviewProxyService, + launcherProxyService = mockLauncherProxyService, metricsLogger = mockEduMetricsLogger, clock = fakeEduClock, ) } var Kosmos.mockEduMetricsLogger by Kosmos.Fixture { mock<ContextualEducationMetricsLogger>() } -var Kosmos.mockOverviewProxyService by Kosmos.Fixture { mock<OverviewProxyService>() } +var Kosmos.mockLauncherProxyService by Kosmos.Fixture { mock<LauncherProxyService>() } var Kosmos.mockEduInputManager by Kosmos.Fixture { mock<InputManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index f4791003c828..026f8f97d2ae 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -124,8 +124,8 @@ class FakeKeyguardTransitionRepository( /** * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step. * - * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part - * way using [throughTransitionState]. + * By default, sends steps through FINISHED (STARTED, RUNNING @0.5f, RUNNING @1f, FINISHED) but + * can be halted part way using [throughTransitionState]. */ suspend fun sendTransitionSteps( from: KeyguardState, @@ -137,6 +137,25 @@ class FakeKeyguardTransitionRepository( } /** + * Sends a STARTED step between [from] and [to], followed by two RUNNING steps at value + * [throughValue] / 2 and [throughValue], calling [runCurrent] after each step. + */ + suspend fun sendTransitionStepsThroughRunning( + from: KeyguardState, + to: KeyguardState, + testScope: TestScope, + throughValue: Float = 1f, + ) { + sendTransitionSteps( + from, + to, + testScope.testScheduler, + TransitionState.RUNNING, + throughValue, + ) + } + + /** * Sends the provided [step] and makes sure that all previous [TransitionState]'s are sent when * [fillInSteps] is true. e.g. when a step FINISHED is provided, a step with STARTED and RUNNING * is also sent. @@ -178,14 +197,15 @@ class FakeKeyguardTransitionRepository( /** * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step. * - * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part - * way using [throughTransitionState]. + * By default, sends steps through FINISHED (STARTED, RUNNING @0.5f, RUNNING @1f, FINISHED) but + * can be halted part way using [throughTransitionState]. */ suspend fun sendTransitionSteps( from: KeyguardState, to: KeyguardState, testScheduler: TestCoroutineScheduler, throughTransitionState: TransitionState = TransitionState.FINISHED, + throughTransitionValue: Float = 1f, ) { val lastStep = _transitions.replayCache.lastOrNull() if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) { @@ -216,13 +236,14 @@ class FakeKeyguardTransitionRepository( throughTransitionState == TransitionState.RUNNING || throughTransitionState == TransitionState.FINISHED ) { + // Send two steps to better simulate RUNNING transitions. sendTransitionStep( step = TransitionStep( transitionState = TransitionState.RUNNING, from = from, to = to, - value = 0.5f, + value = throughTransitionValue / 2f, ) ) testScheduler.runCurrent() @@ -233,7 +254,7 @@ class FakeKeyguardTransitionRepository( transitionState = TransitionState.RUNNING, from = from, to = to, - value = 1f, + value = throughTransitionValue, ) ) testScheduler.runCurrent() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt index 004f97d95673..c97c4e3ba302 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt @@ -25,5 +25,6 @@ val Kosmos.occludedToPrimaryBouncerTransitionViewModel by Fixture { OccludedToPrimaryBouncerTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, blurConfig = blurConfig, + shadeDependentFlows = shadeDependentFlows, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt index 2256c10eebc9..ed5dd454a087 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt @@ -25,5 +25,6 @@ val Kosmos.primaryBouncerToOccludedTransitionViewModel by Fixture { PrimaryBouncerToOccludedTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, blurConfig = blurConfig, + shadeDependentFlows = shadeDependentFlows, ) } diff --git a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java index 81c7edf4adf1..b9dcc6160d68 100644 --- a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java +++ b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java @@ -32,8 +32,7 @@ import com.android.tools.r8.keepanno.annotations.UsedByReflection; // Without this annotation, this class will be treated as unused class and be removed during build // time. @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS) -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public final class ConnectivityServiceInitializerB extends SystemService { private static final String TAG = ConnectivityServiceInitializerB.class.getSimpleName(); private final VcnManagementService mVcnManagementService; diff --git a/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java index c9a99d729e91..8edd63dc341f 100644 --- a/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java +++ b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java @@ -165,8 +165,7 @@ import java.util.concurrent.TimeUnit; * @hide */ // TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private static final String TAG = VcnManagementService.class.getSimpleName(); @NonNull private static final String CONTEXT_ATTRIBUTION_TAG = "VCN"; diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java index b04e25dff276..cedb2d16808f 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -79,8 +79,7 @@ import java.util.Set; * * @hide */ -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName(); private static final boolean LOG_DBG = false; // STOPSHIP if true diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java index 97f86b1bff5b..0f8b2885cf4d 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java @@ -77,8 +77,7 @@ import java.util.Set; * * @hide */ -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class Vcn extends Handler { private static final String TAG = Vcn.class.getSimpleName(); diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java index 300b80f942ef..da411174f95c 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java @@ -174,8 +174,7 @@ import java.util.function.Consumer; * * @hide */ -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java index 38fcf09145d9..bc815eb27454 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java @@ -58,8 +58,7 @@ import java.util.concurrent.Executor; */ // TODO(b/388919146): Implement a more generic solution to prevent concurrent modifications on // mListeners and mRequests -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class VcnNetworkProvider extends NetworkProvider { private static final String TAG = VcnNetworkProvider.class.getSimpleName(); diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index c8c645f1276d..aff7068b6c4f 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -61,8 +61,7 @@ import java.util.concurrent.TimeUnit; * * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state" */ -// TODO(b/374174952) Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class IpSecPacketLossDetector extends NetworkMetricMonitor { private static final String TAG = IpSecPacketLossDetector.class.getSimpleName(); diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java index 55829a5fe978..fc9c7ac8a335 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java @@ -44,8 +44,7 @@ import java.util.concurrent.Executor; * * <p>This class is flag gated by "network_metric_monitor" */ -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public abstract class NetworkMetricMonitor implements AutoCloseable { private static final String TAG = NetworkMetricMonitor.class.getSimpleName(); diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java index 705141f3f1b4..7cb3257193a5 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -52,8 +52,7 @@ import java.util.Map; import java.util.Set; /** @hide */ -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) class NetworkPriorityClassifier { @NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName(); /** diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java index bc552e7e6afd..37ec0e8f40dc 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -75,8 +75,7 @@ import java.util.TreeSet; * * @hide */ -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class UnderlyingNetworkController { @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName(); diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java index 776931bad73b..164b59f6f0cd 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java @@ -52,8 +52,7 @@ import java.util.concurrent.TimeUnit; * * @hide */ -// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization -@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@TargetApi(Build.VERSION_CODES.BAKLAVA) public class UnderlyingNetworkEvaluator { private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 91775f8eed96..8e0a7785c597 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -607,7 +607,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mLock, mContext, new MagnificationScaleProvider(mContext), - Executors.newSingleThreadExecutor() + Executors.newSingleThreadExecutor(), + mContext.getMainLooper() ); mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); mCaptioningManagerImpl = new CaptioningManagerImpl(mContext); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 75ec8ea88ace..486f1f449691 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -36,6 +36,8 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -53,6 +55,7 @@ import android.view.accessibility.MagnificationAnimationCallback; import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.wm.WindowManagerInternal; @@ -111,6 +114,20 @@ public class MagnificationController implements MagnificationConnectionManager.C private final Executor mBackgroundExecutor; + private final Handler mHandler; + private @PanDirection int mActivePanDirection = PAN_DIRECTION_DOWN; + private int mActivePanDisplay = Display.INVALID_DISPLAY; + private boolean mRepeatKeysEnabled = true; + + private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN; + private int mActiveZoomDisplay = Display.INVALID_DISPLAY; + + // TODO(b/355499907): Get initial repeat interval from repeat keys settings. + @VisibleForTesting + public static final int INITIAL_KEYBOARD_REPEAT_INTERVAL_MS = 500; + @VisibleForTesting + public static final int KEYBOARD_REPEAT_INTERVAL_MS = 60; + @GuardedBy("mLock") private final SparseIntArray mCurrentMagnificationModeArray = new SparseIntArray(); @GuardedBy("mLock") @@ -287,12 +304,13 @@ public class MagnificationController implements MagnificationConnectionManager.C public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, MagnificationScaleProvider scaleProvider, - Executor backgroundExecutor) { + Executor backgroundExecutor, Looper looper) { mAms = ams; mLock = lock; mContext = context; mScaleProvider = scaleProvider; mBackgroundExecutor = backgroundExecutor; + mHandler = new Handler(looper); LocalServices.getService(WindowManagerInternal.class) .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this); mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( @@ -303,14 +321,20 @@ public class MagnificationController implements MagnificationConnectionManager.C mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context); mAlwaysOnMagnificationFeatureFlag.addOnChangedListener( mBackgroundExecutor, mAms::updateAlwaysOnMagnification); + + // TODO(b/355499907): Add an observer for repeat keys enabled changes, + // rather than initializing once at startup. + mRepeatKeysEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_ENABLED, 1, + UserHandle.USER_CURRENT) != 0; } @VisibleForTesting public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, FullScreenMagnificationController fullScreenMagnificationController, MagnificationConnectionManager magnificationConnectionManager, - MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) { - this(ams, lock, context, scaleProvider, backgroundExecutor); + MagnificationScaleProvider scaleProvider, Executor backgroundExecutor, Looper looper) { + this(ams, lock, context, scaleProvider, backgroundExecutor, looper); mFullScreenMagnificationController = fullScreenMagnificationController; mMagnificationConnectionManager = magnificationConnectionManager; } @@ -354,27 +378,60 @@ public class MagnificationController implements MagnificationConnectionManager.C // pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same // speed as non-diagonal movement. panMagnificationByStep(displayId, direction); + mActivePanDirection = direction; + mActivePanDisplay = displayId; + if (mRepeatKeysEnabled) { + mHandler.sendMessageDelayed( + PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this), + INITIAL_KEYBOARD_REPEAT_INTERVAL_MS); + } } @Override public void onPanMagnificationStop(int displayId, @MagnificationController.PanDirection int direction) { - // TODO(b/388847283): Handle held key gestures, which can be used - // for continuous scaling and panning, until they are released. - + if (direction == mActivePanDirection) { + mActivePanDisplay = Display.INVALID_DISPLAY; + } } @Override public void onScaleMagnificationStart(int displayId, @MagnificationController.ZoomDirection int direction) { scaleMagnificationByStep(displayId, direction); + mActiveZoomDirection = direction; + mActiveZoomDisplay = displayId; + if (mRepeatKeysEnabled) { + mHandler.sendMessageDelayed( + PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), + INITIAL_KEYBOARD_REPEAT_INTERVAL_MS); + } } @Override public void onScaleMagnificationStop(int displayId, @MagnificationController.ZoomDirection int direction) { - // TODO(b/388847283): Handle held key gestures, which can be used - // for continuous scaling and panning, until they are released. + if (direction == mActiveZoomDirection) { + mActiveZoomDisplay = Display.INVALID_DISPLAY; + } + } + + private void maybeContinuePan() { + if (mActivePanDisplay != Display.INVALID_DISPLAY) { + panMagnificationByStep(mActivePanDisplay, mActivePanDirection); + mHandler.sendMessageDelayed( + PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this), + KEYBOARD_REPEAT_INTERVAL_MS); + } + } + + private void maybeContinueZoom() { + if (mActiveZoomDisplay != Display.INVALID_DISPLAY) { + scaleMagnificationByStep(mActiveZoomDisplay, mActiveZoomDirection); + mHandler.sendMessageDelayed( + PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), + KEYBOARD_REPEAT_INTERVAL_MS); + } } private void handleUserInteractionChanged(int displayId, int mode) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 25494fce9fbf..c68e54956c99 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -19,7 +19,6 @@ package com.android.server.autofill; import static android.Manifest.permission.MANAGE_AUTO_FILL; import static android.content.Context.AUTOFILL_MANAGER_SERVICE; import static android.service.autofill.Flags.fixGetAutofillComponent; -import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; @@ -71,7 +70,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TimeUtils; -import android.view.InsetsController; import android.view.autofill.AutofillFeatureFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; @@ -98,7 +96,6 @@ import com.android.server.autofill.ui.AutoFillUI; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; import com.android.server.infra.SecureSettingsServiceNameResolver; -import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -246,8 +243,6 @@ public final class AutofillManagerService private static final boolean DEFAULT_PCC_USE_FALLBACK = true; - private static final boolean DBG = false; - public AutofillManagerService(Context context) { super(context, new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE), @@ -306,79 +301,8 @@ public final class AutofillManagerService mCredentialAutofillService = null; Slog.w(TAG, "Invalid CredentialAutofillService"); } - - if (improveFillDialogAconfig()) { - WindowManagerInternal windowManagerInternal = LocalServices.getService( - WindowManagerInternal.class); - WindowManagerInternal.ImeInsetsAnimationChangeListener - imeInsetsAnimationChangeListener = - new WindowManagerInternal.ImeInsetsAnimationChangeListener() { - @Override - public void onAnimationStart( - @InsetsController.AnimationType int animationType, int userId) { - if (DBG) { - Slog.e(TAG, - "onAnimationStart() notifyImeAnimationStart() " - + "animationType:" - + String.valueOf(animationType)); - } - synchronized (mLock) { - - // We are mostly interested in animations that show up the IME - if (animationType == InsetsController.ANIMATION_TYPE_HIDE) { - // IME is going away - mIsImeShowing = false; - } - if (animationType != InsetsController.ANIMATION_TYPE_SHOW) { - return; - } - mIsImeShowing = true; - mImeAnimatingWhileShowingUp = true; - final AutofillManagerServiceImpl service = - peekServiceForUserWithLocalBinderIdentityLocked(userId); - if (service != null) { - service.notifyImeAnimationStart(); - } else if (sVerbose) { - Slog.v(TAG, - "notifyImeAnimationStart(): no service for " + userId); - } - } - } - - @Override - public void onAnimationEnd( - @InsetsController.AnimationType int animationType, int userId) { - if (DBG) { - Slog.e(TAG, - "onAnimationEnd() notifyImeAnimationEnd() " - + "animationType:" - + String.valueOf(animationType)); - } - // We are only interested in animations that show up the IME - if (animationType != InsetsController.ANIMATION_TYPE_SHOW) { - return; - } - mImeAnimatingWhileShowingUp = false; - synchronized (mLock) { - final AutofillManagerServiceImpl service = - peekServiceForUserWithLocalBinderIdentityLocked(userId); - if (service != null) { - service.notifyImeAnimationEnd(); - } else if (sVerbose) { - Slog.v(TAG, "notifyImeAnimationEnd(): no service for " - + userId); - } - } - } - }; - windowManagerInternal.setImeInsetsAnimationChangeListener( - imeInsetsAnimationChangeListener); - } } - public boolean mImeAnimatingWhileShowingUp = false; - public boolean mIsImeShowing = false; - @Override // from AbstractMasterSystemService protected String getServiceSettingsProperty() { return Settings.Secure.AUTOFILL_SERVICE; diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index eda62334ff39..b39b5b1a7660 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -209,11 +209,6 @@ final class AutofillManagerServiceImpl private final DisabledInfoCache mDisabledInfoCache; - // Tracks active session id. There is no guarantee that such a session exists. For eg, if the - // session is destroyed, the id may no longer be valid. We don't update the state in all the - // cases. - private int mActiveSessionId = NO_SESSION; - AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, @@ -392,7 +387,6 @@ final class AutofillManagerServiceImpl @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, @NonNull ComponentName clientActivity, boolean compatMode, boolean bindInstantServiceAllowed, int flags) { - mActiveSessionId = NO_SESSION; // FLAG_AUGMENTED_AUTOFILL_REQUEST is set in the flags when standard autofill is disabled // but the package is allowlisted for augmented autofill boolean forAugmentedAutofillOnly = (flags @@ -451,7 +445,6 @@ final class AutofillManagerServiceImpl if (newSession == null) { return NO_SESSION; } - mActiveSessionId = newSession.id; // Service can be null when it's only for augmented autofill String servicePackageName = mInfo == null ? null : mInfo.getServiceInfo().packageName; @@ -755,7 +748,6 @@ final class AutofillManagerServiceImpl Slog.d(TAG, "restarting session " + sessionId + " due to manual request on " + autofillId); } - mActiveSessionId = sessionId; return true; } if (sVerbose) { @@ -765,8 +757,6 @@ final class AutofillManagerServiceImpl return false; } - - mActiveSessionId = sessionId; session.updateLocked(autofillId, virtualBounds, value, action, flags); return false; } @@ -886,54 +876,21 @@ final class AutofillManagerServiceImpl } @GuardedBy("mLock") - public void notifyImeAnimationStart() { - if (!isEnabledLocked()) { - Slog.wtf(TAG, "Service not enabled"); - return; - } - final Session session = mSessions.get(mActiveSessionId); - if (session == null) { - Slog.v(TAG, "notifyImeAnimationEnd(): no session for " + mActiveSessionId); - return; - } - session.notifyImeAnimationStart(SystemClock.elapsedRealtime()); - } - - @GuardedBy("mLock") public void notifyImeAnimationEnd(int sessionId, long endTimeMs, int uid) { if (!isEnabledLocked()) { Slog.wtf(TAG, "Service not enabled"); return; } final Session session = mSessions.get(sessionId); - if (session == null) { + if (session == null || uid != session.uid) { Slog.v(TAG, "notifyImeAnimationEnd(): no session for " + sessionId + "(" + uid + ")"); return; } - if (uid != session.uid) { - Slog.v(TAG, "notifyImeAnimationEnd(): Mismatched session id's " - + sessionId + "(" + uid + ")"); - return; - } session.notifyImeAnimationEnd(endTimeMs); } @GuardedBy("mLock") - public void notifyImeAnimationEnd() { - if (!isEnabledLocked()) { - Slog.wtf(TAG, "Service not enabled"); - return; - } - final Session session = mSessions.get(mActiveSessionId); - if (session == null) { - Slog.v(TAG, "notifyImeAnimationEnd(): no session for " + mActiveSessionId); - return; - } - session.notifyImeAnimationEnd(SystemClock.elapsedRealtime()); - } - - @GuardedBy("mLock") @Override // from PerUserSystemService protected void handlePackageUpdateLocked(@NonNull String packageName) { final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo(); diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 3817ba1a28b9..2c0366e6a6db 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -24,11 +24,14 @@ import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; import static android.os.Process.ROOT_UID; import static android.os.Process.SYSTEM_UID; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.window.flags.Flags.balClearAllowlistDuration; import android.annotation.IntDef; import android.annotation.Nullable; @@ -305,6 +308,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub { this.stringName = null; } + @VisibleForTesting TempAllowListDuration getAllowlistDurationLocked(IBinder allowlistToken) { + return mAllowlistDuration.get(allowlistToken); + } + void setAllowBgActivityStarts(IBinder token, int flags) { if (token == null) return; if ((flags & FLAG_ACTIVITY_SENDER) != 0) { @@ -323,6 +330,13 @@ public final class PendingIntentRecord extends IIntentSender.Stub { mAllowBgActivityStartsForActivitySender.remove(token); mAllowBgActivityStartsForBroadcastSender.remove(token); mAllowBgActivityStartsForServiceSender.remove(token); + if (mAllowlistDuration != null && balClearAllowlistDuration()) { + TempAllowListDuration duration = mAllowlistDuration.get(token); + if (duration != null + && duration.type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { + duration.type = TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; + } + } } public void registerCancelListenerLocked(IResultReceiver receiver) { @@ -703,7 +717,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return res; } - private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( + @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( IBinder allowlistToken) { return mAllowBgActivityStartsForActivitySender.contains(allowlistToken) ? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken) diff --git a/services/core/java/com/android/server/display/plugin/PluginManager.java b/services/core/java/com/android/server/display/plugin/PluginManager.java index d4099975cafa..cb0a4574361a 100644 --- a/services/core/java/com/android/server/display/plugin/PluginManager.java +++ b/services/core/java/com/android/server/display/plugin/PluginManager.java @@ -74,15 +74,17 @@ public class PluginManager { /** * Adds change listener for particular plugin type */ - public <T> void subscribe(PluginType<T> type, PluginChangeListener<T> listener) { - mPluginStorage.addListener(type, listener); + public <T> void subscribe(PluginType<T> type, String uniqueDisplayId, + PluginChangeListener<T> listener) { + mPluginStorage.addListener(type, uniqueDisplayId, listener); } /** * Removes change listener */ - public <T> void unsubscribe(PluginType<T> type, PluginChangeListener<T> listener) { - mPluginStorage.removeListener(type, listener); + public <T> void unsubscribe(PluginType<T> type, String uniqueDisplayId, + PluginChangeListener<T> listener) { + mPluginStorage.removeListener(type, uniqueDisplayId, listener); } /** diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java index dd3415fb614d..5102c2709329 100644 --- a/services/core/java/com/android/server/display/plugin/PluginStorage.java +++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java @@ -20,10 +20,13 @@ import android.annotation.Nullable; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.tools.r8.keepanno.annotations.KeepForApi; import java.io.PrintWriter; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -35,42 +38,97 @@ import java.util.Set; public class PluginStorage { private static final String TAG = "PluginStorage"; + // Special ID used to indicate that given value is to be applied globally, rather than to a + // specific display. If both GLOBAL and specific display values are present - specific display + // value is selected. + @VisibleForTesting + static final String GLOBAL_ID = "GLOBAL"; + private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<PluginType<?>, Object> mValues = new HashMap<>(); + private final Map<PluginType<?>, ValuesContainer<?>> mValues = new HashMap<>(); @GuardedBy("mLock") private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>(); @GuardedBy("mLock") - private final PluginEventStorage mPluginEventStorage = new PluginEventStorage(); + private final Map<String, PluginEventStorage> mPluginEventStorages = new HashMap<>(); + + /** + * Updates value in storage and forwards it to corresponding listeners for all displays + * that does not have display specific value. + * Should be called by OEM Plugin implementation in order to communicate with Framework + */ + @KeepForApi + public <T> void updateGlobalValue(PluginType<T> type, @Nullable T value) { + updateValue(type, GLOBAL_ID, value); + } /** - * Updates value in storage and forwards it to corresponding listeners. - * Should be called by OEM Plugin implementation in order to provide communicate with Framework + * Updates value in storage and forwards it to corresponding listeners for specific display. + * Should be called by OEM Plugin implementation in order to communicate with Framework + * @param type - plugin type, that need to be updated + * @param uniqueDisplayId - uniqueDisplayId that this type/value should be applied to + * @param value - plugin value for particular type and display */ @KeepForApi - public <T> void updateValue(PluginType<T> type, @Nullable T value) { - Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value); + public <T> void updateValue(PluginType<T> type, String uniqueDisplayId, @Nullable T value) { + Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value + + "; displayId=" + uniqueDisplayId); Set<PluginManager.PluginChangeListener<T>> localListeners; + T valueToNotify; synchronized (mLock) { - mValues.put(type, value); - mPluginEventStorage.onValueUpdated(type); - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - localListeners = new LinkedHashSet<>(container.mListeners); + ValuesContainer<T> valuesByType = getValuesContainerLocked(type); + valuesByType.updateValueLocked(uniqueDisplayId, value); + // if value was set to null, we might need to notify with GLOBAL value instead + valueToNotify = valuesByType.getValueLocked(uniqueDisplayId); + + PluginEventStorage storage = mPluginEventStorages.computeIfAbsent(uniqueDisplayId, + d -> new PluginEventStorage()); + storage.onValueUpdated(type); + + localListeners = getListenersForUpdateLocked(type, uniqueDisplayId); } Slog.d(TAG, "updateValue, notifying listeners=" + localListeners); - localListeners.forEach(l -> l.onChanged(value)); + localListeners.forEach(l -> l.onChanged(valueToNotify)); + } + + @GuardedBy("mLock") + private <T> Set<PluginManager.PluginChangeListener<T>> getListenersForUpdateLocked( + PluginType<T> type, String uniqueDisplayId) { + ListenersContainer<T> listenersContainer = getListenersContainerLocked(type); + Set<PluginManager.PluginChangeListener<T>> localListeners = new LinkedHashSet<>(); + // if GLOBAL value change we need to notify only listeners for displays that does not + // have display specific value + if (GLOBAL_ID.equals(uniqueDisplayId)) { + ValuesContainer<T> valuesContainer = getValuesContainerLocked(type); + Set<String> excludedDisplayIds = valuesContainer.getNonGlobalDisplaysLocked(); + listenersContainer.mListeners.forEach((localDisplayId, listeners) -> { + if (!excludedDisplayIds.contains(localDisplayId)) { + localListeners.addAll(listeners); + } + }); + } else { + localListeners.addAll( + listenersContainer.mListeners.getOrDefault(uniqueDisplayId, Set.of())); + } + return localListeners; } /** * Adds listener for PluginType. If storage already has value for this type, listener will * be notified immediately. */ - <T> void addListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) { + <T> void addListener(PluginType<T> type, String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + if (GLOBAL_ID.equals(uniqueDisplayId)) { + Slog.d(TAG, "addListener ignored for GLOBAL_ID, type=" + type.mName); + return; + } T value = null; synchronized (mLock) { - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - if (container.mListeners.add(listener)) { - value = getValueForTypeLocked(type); + ListenersContainer<T> container = getListenersContainerLocked(type); + if (container.addListenerLocked(uniqueDisplayId, listener)) { + ValuesContainer<T> valuesContainer = getValuesContainerLocked(type); + value = valuesContainer.getValueLocked(uniqueDisplayId); } } if (value != null) { @@ -81,10 +139,15 @@ public class PluginStorage { /** * Removes listener */ - <T> void removeListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) { + <T> void removeListener(PluginType<T> type, String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + if (GLOBAL_ID.equals(uniqueDisplayId)) { + Slog.d(TAG, "removeListener ignored for GLOBAL_ID, type=" + type.mName); + return; + } synchronized (mLock) { - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - container.mListeners.remove(listener); + ListenersContainer<T> container = getListenersContainerLocked(type); + container.removeListenerLocked(uniqueDisplayId, listener); } } @@ -92,53 +155,106 @@ public class PluginStorage { * Print the object's state and debug information into the given stream. */ void dump(PrintWriter pw) { - Map<PluginType<?>, Object> localValues; + Map<PluginType<?>, Map<String, Object>> localValues = new HashMap<>(); @SuppressWarnings("rawtypes") - Map<PluginType, Set> localListeners = new HashMap<>(); - List<PluginEventStorage.TimeFrame> timeFrames; + Map<PluginType, Map<String, Set>> localListeners = new HashMap<>(); + Map<String, List<PluginEventStorage.TimeFrame>> timeFrames = new HashMap<>(); synchronized (mLock) { - timeFrames = mPluginEventStorage.getTimeFrames(); - localValues = new HashMap<>(mValues); - mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners)); + mPluginEventStorages.forEach((displayId, storage) -> { + timeFrames.put(displayId, storage.getTimeFrames()); + }); + mValues.forEach((type, valueContainer) -> { + localValues.put(type, new HashMap<>(valueContainer.mValues)); + }); + mListeners.forEach((type, container) -> { + localListeners.put(type, new HashMap<>(container.mListeners)); + }); } pw.println("PluginStorage:"); pw.println("values=" + localValues); pw.println("listeners=" + localListeners); pw.println("PluginEventStorage:"); - for (PluginEventStorage.TimeFrame timeFrame: timeFrames) { - timeFrame.dump(pw); + for (Map.Entry<String, List<PluginEventStorage.TimeFrame>> timeFrameEntry : + timeFrames.entrySet()) { + pw.println("TimeFrames for displayId=" + timeFrameEntry.getKey()); + for (PluginEventStorage.TimeFrame timeFrame : timeFrameEntry.getValue()) { + timeFrame.dump(pw); + } } } @GuardedBy("mLock") @SuppressWarnings("unchecked") - private <T> T getValueForTypeLocked(PluginType<T> type) { - Object value = mValues.get(type); - if (value == null) { - return null; - } else if (type.mType == value.getClass()) { - return (T) value; + private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) { + ListenersContainer<?> container = mListeners.get(type); + if (container == null) { + ListenersContainer<T> lc = new ListenersContainer<>(); + mListeners.put(type, lc); + return lc; } else { - Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName() - + ", expected=" + type.mType.getName()); - return null; + return (ListenersContainer<T>) container; } } @GuardedBy("mLock") @SuppressWarnings("unchecked") - private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) { - ListenersContainer<?> container = mListeners.get(type); + private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) { + ValuesContainer<?> container = mValues.get(type); if (container == null) { - ListenersContainer<T> lc = new ListenersContainer<>(); - mListeners.put(type, lc); - return lc; + ValuesContainer<T> vc = new ValuesContainer<>(); + mValues.put(type, vc); + return vc; } else { - return (ListenersContainer<T>) container; + return (ValuesContainer<T>) container; } } private static final class ListenersContainer<T> { - private final Set<PluginManager.PluginChangeListener<T>> mListeners = new LinkedHashSet<>(); + private final Map<String, Set<PluginManager.PluginChangeListener<T>>> mListeners = + new LinkedHashMap<>(); + + private boolean addListenerLocked( + String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener) { + Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = + mListeners.computeIfAbsent(uniqueDisplayId, k -> new LinkedHashSet<>()); + return listenersForDisplay.add(listener); + } + + private void removeListenerLocked(String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = mListeners.get( + uniqueDisplayId); + if (listenersForDisplay == null) { + return; + } + + listenersForDisplay.remove(listener); + + if (listenersForDisplay.isEmpty()) { + mListeners.remove(uniqueDisplayId); + } + } + } + + private static final class ValuesContainer<T> { + private final Map<String, T> mValues = new HashMap<>(); + + private void updateValueLocked(String uniqueDisplayId, @Nullable T value) { + if (value == null) { + mValues.remove(uniqueDisplayId); + } else { + mValues.put(uniqueDisplayId, value); + } + } + + private Set<String> getNonGlobalDisplaysLocked() { + Set<String> keys = new HashSet<>(mValues.keySet()); + keys.remove(GLOBAL_ID); + return keys; + } + + private @Nullable T getValueLocked(String displayId) { + return mValues.getOrDefault(displayId, mValues.get(GLOBAL_ID)); + } } } diff --git a/services/core/java/com/android/server/infra/OWNERS b/services/core/java/com/android/server/infra/OWNERS index 4fea05d295b6..0f0d382e28f8 100644 --- a/services/core/java/com/android/server/infra/OWNERS +++ b/services/core/java/com/android/server/infra/OWNERS @@ -1,3 +1,4 @@ # Bug component: 655446 srazdan@google.com +reemabajwa@google.com diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index b9352edf9230..ddace179348c 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -42,6 +42,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.util.Collection; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -372,10 +373,12 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub /* package */ void onEndpointSessionOpenRequest( int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { - boolean success = + Optional<Byte> error = onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor); - if (!success) { - cleanupSessionResources(sessionId); + if (error.isPresent()) { + halCloseEndpointSessionNoThrow(sessionId, error.get()); + onCloseEndpointSession(sessionId, error.get()); + // Resource cleanup is done in onCloseEndpointSession } } @@ -422,7 +425,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } } - private boolean onEndpointSessionOpenRequestInternal( + private Optional<Byte> onEndpointSessionOpenRequestInternal( int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { if (!hasEndpointPermissions(initiator)) { Log.e( @@ -431,22 +434,21 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub + initiator + " doesn't have permission for " + mEndpointInfo); - halCloseEndpointSessionNoThrow(sessionId, Reason.PERMISSION_DENIED); - return false; + return Optional.of(Reason.PERMISSION_DENIED); } synchronized (mOpenSessionLock) { if (hasSessionId(sessionId)) { Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId); - halCloseEndpointSessionNoThrow(sessionId, Reason.UNSPECIFIED); - return false; + return Optional.of(Reason.UNSPECIFIED); } mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true)); } - return invokeCallback( + boolean success = invokeCallback( (consumer) -> consumer.onSessionOpenRequest(sessionId, initiator, serviceDescriptor)); + return success ? Optional.empty() : Optional.of(Reason.UNSPECIFIED); } private byte onMessageReceivedInternal(int sessionId, HubMessage message) { diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java index 2e0bb4f88485..18f2f48b80a3 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.telecom.TelecomManager; import android.telephony.TelephonyCallback; @@ -38,6 +39,7 @@ import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemConfig; +import java.util.List; import java.util.function.Consumer; /** @@ -60,21 +62,35 @@ public class MediaProjectionStopController { private final TelephonyManager mTelephonyManager; private final AppOpsManager mAppOpsManager; private final PackageManager mPackageManager; - private final RoleManager mRoleManager; + private final RoleHolderProvider mRoleHolderProvider; private final ContentResolver mContentResolver; private boolean mIsInCall; private long mLastCallStartTimeMillis; + + @VisibleForTesting + interface RoleHolderProvider { + List<String> getRoleHoldersAsUser(String roleName, UserHandle user); + } + public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) { + this(context, stopReasonConsumer, + (roleName, user) -> context.getSystemService(RoleManager.class) + .getRoleHoldersAsUser(roleName, user)); + } + + @VisibleForTesting + MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer, + RoleHolderProvider roleHolderProvider) { mStopReasonConsumer = stopReasonConsumer; mKeyguardManager = context.getSystemService(KeyguardManager.class); mTelecomManager = context.getSystemService(TelecomManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); mPackageManager = context.getPackageManager(); - mRoleManager = context.getSystemService(RoleManager.class); mContentResolver = context.getContentResolver(); + mRoleHolderProvider = roleHolderProvider; } /** @@ -146,8 +162,9 @@ public class MediaProjectionStopController { Slog.v(TAG, "Continuing MediaProjection for package with OP_PROJECT_MEDIA AppOp "); return true; } - if (mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - projectionGrant.userHandle).contains(projectionGrant.packageName)) { + if (mRoleHolderProvider.getRoleHoldersAsUser( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, projectionGrant.userHandle) + .contains(projectionGrant.packageName)) { Slog.v(TAG, "Continuing MediaProjection for package holding app streaming role."); return true; } @@ -177,10 +194,6 @@ public class MediaProjectionStopController { */ public boolean isStartForbidden( MediaProjectionManagerService.MediaProjection projectionGrant) { - if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { - return false; - } - if (!mKeyguardManager.isKeyguardLocked()) { return false; } @@ -194,9 +207,6 @@ public class MediaProjectionStopController { @VisibleForTesting void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { if (!isKeyguardLocked) return; - if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { - return; - } mStopReasonConsumer.accept(STOP_REASON_KEYGUARD); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0d3c18ac339f..b4a58ac72394 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5056,14 +5056,8 @@ public class NotificationManagerService extends SystemService { } @Override - public ParceledListSlice<NotificationChannel> getNotificationChannels( - String callingPkg, String targetPkg, int userId) { - return getOrCreateNotificationChannels(callingPkg, targetPkg, userId, false); - } - - @Override - public ParceledListSlice<NotificationChannel> getOrCreateNotificationChannels( - String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded) { + public ParceledListSlice<NotificationChannel> getNotificationChannels(String callingPkg, + String targetPkg, int userId) { if (canNotifyAsPackage(callingPkg, targetPkg, userId) || isCallingUidSystem()) { int targetUid = -1; @@ -5073,8 +5067,7 @@ public class NotificationManagerService extends SystemService { /* ignore */ } return mPreferencesHelper.getNotificationChannels( - targetPkg, targetUid, false /* includeDeleted */, true, - createPrefsIfNeeded); + targetPkg, targetUid, false /* includeDeleted */, true); } throw new SecurityException("Pkg " + callingPkg + " cannot read channels for " + targetPkg + " in " + userId); @@ -8505,6 +8498,9 @@ public class NotificationManagerService extends SystemService { (userId == USER_ALL) ? USER_SYSTEM : userId); Notification.addFieldsFromContext(ai, notification); + // can't be set by an app + notification.extras.remove(Notification.EXTRA_SUMMARIZED_CONTENT); + if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) { notification.flags &= ~FLAG_FOREGROUND_SERVICE; } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 3b34dcd17705..14cc91b6305f 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -16,8 +16,8 @@ package com.android.server.notification; -import static android.app.Flags.notificationClassificationUi; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; +import static android.app.Flags.notificationClassificationUi; import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID; import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; @@ -89,9 +89,10 @@ import android.util.SparseBooleanArray; import android.util.StatsEvent; import android.util.proto.ProtoOutputStream; +import androidx.annotation.VisibleForTesting; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.logging.MetricsLogger; @@ -1962,21 +1963,11 @@ public class PreferencesHelper implements RankingConfig { @Override public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted, boolean includeBundles) { - return getNotificationChannels(pkg, uid, includeDeleted, includeBundles, false); - } - - protected ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, - boolean includeDeleted, boolean includeBundles, boolean createPrefsIfNeeded) { - if (createPrefsIfNeeded && !android.app.Flags.nmBinderPerfCacheChannels()) { - Slog.wtf(TAG, - "getNotificationChannels called with createPrefsIfNeeded=true and flag off"); - createPrefsIfNeeded = false; - } Objects.requireNonNull(pkg); List<NotificationChannel> channels = new ArrayList<>(); synchronized (mLock) { PackagePreferences r; - if (createPrefsIfNeeded) { + if (android.app.Flags.nmBinderPerfCacheChannels()) { r = getOrCreatePackagePreferencesLocked(pkg, uid); } else { r = getPackagePreferencesLocked(pkg, uid); @@ -1997,6 +1988,18 @@ public class PreferencesHelper implements RankingConfig { } } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + // Gets the entire list of notification channels for this package, with no filtering and + // without creating package preferences. For testing only, specifically to confirm the + // notification channels of a removed/deleted package. + protected List<NotificationChannel> getRemovedPkgNotificationChannels(String pkg, int uid) { + PackagePreferences r = getPackagePreferencesLocked(pkg, uid); + if (r == null || r.channels == null) { + return new ArrayList<>(); + } + return new ArrayList<>(r.channels.values()); + } + /** * Gets all notification channels associated with the given pkg and uid that can bypass dnd */ diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 977c6db66106..a5185a2139db 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -38,6 +38,7 @@ import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import com.android.internal.util.ArrayUtils; +import com.android.server.power.optimization.Flags; import com.android.server.power.stats.BatteryStatsImpl.BatteryStatsSession; import java.io.PrintWriter; @@ -351,7 +352,7 @@ public class BatteryUsageStatsProvider { accumulatedStats.endMonotonicTime = endMonotonicTime; accumulatedStats.builder.setStatsEndTimestamp(endWallClockTime); - accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime); + accumulatedStats.builder.setStatsDuration(endMonotonicTime - startMonotonicTime); mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, session.getHistory(), startMonotonicTime, endMonotonicTime); @@ -403,7 +404,10 @@ public class BatteryUsageStatsProvider { } if ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) { - batteryUsageStatsBuilder.setBatteryHistory(session.getHistory().copy()); + batteryUsageStatsBuilder.setBatteryHistory(session.getHistory().copy(), + Flags.extendedBatteryHistoryContinuousCollectionEnabled() + ? query.getPreferredHistoryDurationMs() + : Long.MAX_VALUE); } mPowerAttributor.estimatePowerConsumption(batteryUsageStatsBuilder, session.getHistory(), diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 064ef1aa0eff..89b46bc4eba4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -46,6 +46,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isFloating; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -8376,6 +8377,7 @@ final class ActivityRecord extends WindowToken { mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; + // TODO(b/392069771): Move to AppCompatSandboxingPolicy. // Sandbox max bounds by setting it to the activity bounds, if activity is letterboxed, or // has or will have mAppCompatDisplayInsets for size compat. Also forces an activity to be // sandboxed or not depending upon the configuration settings. @@ -8404,6 +8406,20 @@ final class ActivityRecord extends WindowToken { resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } + // Sandbox activity bounds in freeform to app bounds to force app to display within the + // container. This prevents UI cropping when activities can draw below insets which are + // normally excluded from appBounds before targetSDK < 35 + // (see ConfigurationContainer#applySizeOverrideIfNeeded). + if (isFloating(parentWindowingMode)) { + Rect appBounds = resolvedConfig.windowConfiguration.getAppBounds(); + if (appBounds == null || appBounds.isEmpty()) { + // When there is no override bounds, the activity will inherit the bounds from + // parent. + appBounds = mResolveConfigHint.mParentAppBoundsOverride; + } + resolvedConfig.windowConfiguration.setBounds(appBounds); + } + applySizeOverrideIfNeeded( mDisplayContent, info.applicationInfo, diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index c7d4467a6e98..81c7807311dd 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -71,8 +71,6 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_USER_LEAVING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; @@ -91,9 +89,7 @@ import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -157,8 +153,6 @@ import com.android.server.wm.TaskFragment.EmbeddingCheckResult; import com.android.wm.shell.Flags; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.DateFormat; import java.util.Date; import java.util.function.Supplier; @@ -172,8 +166,6 @@ import java.util.function.Supplier; class ActivityStarter { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStarter" : TAG_ATM; private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; - private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; - private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; private static final int INVALID_LAUNCH_MODE = -1; @@ -255,26 +247,7 @@ class ActivityStarter { private boolean mIsTaskCleared; private boolean mMovedToFront; private boolean mNoAnimation; - - // TODO mAvoidMoveToFront before V is changed from a boolean to a int code mCanMoveToFrontCode - // for the purpose of attribution of new BAL V feature. This should be reverted back to the - // boolean flag post V. - @IntDef(prefix = {"MOVE_TO_FRONT_"}, value = { - MOVE_TO_FRONT_ALLOWED, - MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS, - MOVE_TO_FRONT_AVOID_LEGACY, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface MoveToFrontCode {} - - // Allows a task move to front. - private static final int MOVE_TO_FRONT_ALLOWED = 0; - // Avoid a task move to front because the Pending Intent that starts the activity only - // its creator has the BAL privilege, its sender does not. - private static final int MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS = 1; - // Avoid a task move to front because of all other legacy reasons. - private static final int MOVE_TO_FRONT_AVOID_LEGACY = 2; - private @MoveToFrontCode int mCanMoveToFrontCode = MOVE_TO_FRONT_ALLOWED; + private boolean mAvoidMoveToFront; private boolean mFrozeTaskList; private boolean mTransientLaunch; // The task which was above the targetTask before starting this activity. null if the targetTask @@ -771,7 +744,7 @@ class ActivityStarter { mIsTaskCleared = starter.mIsTaskCleared; mMovedToFront = starter.mMovedToFront; mNoAnimation = starter.mNoAnimation; - mCanMoveToFrontCode = starter.mCanMoveToFrontCode; + mAvoidMoveToFront = starter.mAvoidMoveToFront; mFrozeTaskList = starter.mFrozeTaskList; mVoiceSession = starter.mVoiceSession; @@ -1711,14 +1684,6 @@ class ActivityStarter { return result; } - private boolean avoidMoveToFront() { - return mCanMoveToFrontCode != MOVE_TO_FRONT_ALLOWED; - } - - private boolean avoidMoveToFrontPIOnlyCreatorAllows() { - return mCanMoveToFrontCode == MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS; - } - /** * If the start result is success, ensure that the configuration of the started activity matches * the current display. Otherwise clean up unassociated containers to avoid leakage. @@ -1768,7 +1733,7 @@ class ActivityStarter { startedActivityRootTask.setAlwaysOnTop(true); } - if (isIndependentLaunch && !mDoResume && avoidMoveToFront() && !mTransientLaunch + if (isIndependentLaunch && !mDoResume && mAvoidMoveToFront && !mTransientLaunch && !started.shouldBeVisible(true /* ignoringKeyguard */)) { Slog.i(TAG, "Abort " + transition + " of invisible launch " + started); transition.abort(); @@ -1784,7 +1749,7 @@ class ActivityStarter { currentTop, currentTop.mDisplayContent, false /* deferResume */); } - if (!avoidMoveToFront() && mDoResume + if (!mAvoidMoveToFront && mDoResume && !mService.getUserManagerInternal().isVisibleBackgroundFullUser(started.mUserId) && mRootWindowContainer.hasVisibleWindowAboveButDoesNotOwnNotificationShade( started.launchedFromUid)) { @@ -1934,19 +1899,17 @@ class ActivityStarter { } // When running transient transition, the transient launch target should keep on top. // So disallow the transient hide activity to move itself to front, e.g. trampoline. - if (!avoidMoveToFront() && (mService.mHomeProcess == null + if (!mAvoidMoveToFront && (mService.mHomeProcess == null || mService.mHomeProcess.mUid != realCallingUid) && (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents()) && r.mTransitionController.isTransientHide(targetTask)) { - mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY; + mAvoidMoveToFront = true; } // If the activity is started by sending a pending intent and only its creator has the // privilege to allow BAL (its sender does not), avoid move it to the front. Only do // this when it is not a new task and not already been marked as avoid move to front. - // Guarded by a flag: balDontBringExistingBackgroundTaskStackToFg - if (balDontBringExistingBackgroundTaskStackToFg() && !avoidMoveToFront() - && balVerdict.onlyCreatorAllows()) { - mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS; + if (!mAvoidMoveToFront && balVerdict.onlyCreatorAllows()) { + mAvoidMoveToFront = true; } mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask()); } @@ -2003,32 +1966,28 @@ class ActivityStarter { // After activity is attached to task, but before actual start recordTransientLaunchIfNeeded(mLastStartActivityRecord); - if (mDoResume) { - if (!avoidMoveToFront()) { - mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask); - - final boolean launchBehindDream; - if (com.android.window.flags.Flags.removeActivityStarterDreamCallback()) { - final TaskDisplayArea tda = mTargetRootTask.getTaskDisplayArea(); - final Task top = (tda != null ? tda.getTopRootTask() : null); - launchBehindDream = (top != null && top != mTargetRootTask) - && top.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_DREAM - && top.getTopNonFinishingActivity() != null; - } else { - launchBehindDream = !mTargetRootTask.isTopRootTaskInDisplayArea() - && mService.isDreaming() - && !dreamStopping; - } + if (!mAvoidMoveToFront && mDoResume) { + mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask); - if (launchBehindDream) { - // Launching underneath dream activity (fullscreen, always-on-top). Run the - // launch--behind transition so the Activity gets created and starts - // in visible state. - mLaunchTaskBehind = true; - r.mLaunchTaskBehind = true; - } + final boolean launchBehindDream; + if (com.android.window.flags.Flags.removeActivityStarterDreamCallback()) { + final TaskDisplayArea tda = mTargetRootTask.getTaskDisplayArea(); + final Task top = (tda != null ? tda.getTopRootTask() : null); + launchBehindDream = (top != null && top != mTargetRootTask) + && top.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_DREAM + && top.getTopNonFinishingActivity() != null; } else { - logPIOnlyCreatorAllowsBAL(); + launchBehindDream = !mTargetRootTask.isTopRootTaskInDisplayArea() + && mService.isDreaming() + && !dreamStopping; + } + + if (launchBehindDream) { + // Launching underneath dream activity (fullscreen, always-on-top). Run the + // launch--behind transition so the Activity gets created and starts + // in visible state. + mLaunchTaskBehind = true; + r.mLaunchTaskBehind = true; } } @@ -2089,13 +2048,9 @@ class ActivityStarter { // root-task to the will not update the focused root-task. If starting the new // activity now allows the task root-task to be focusable, then ensure that we // now update the focused root-task accordingly. - if (mTargetRootTask.isTopActivityFocusable() + if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable() && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) { - if (!avoidMoveToFront()) { - mTargetRootTask.moveToFront("startActivityInner"); - } else { - logPIOnlyCreatorAllowsBAL(); - } + mTargetRootTask.moveToFront("startActivityInner"); } mRootWindowContainer.resumeFocusedTasksTopActivities( mTargetRootTask, mStartActivity, mOptions, mTransientLaunch); @@ -2123,26 +2078,6 @@ class ActivityStarter { return START_SUCCESS; } - // TODO (b/316135632) Post V release, remove this log method. - private void logPIOnlyCreatorAllowsBAL() { - if (!avoidMoveToFrontPIOnlyCreatorAllows()) return; - String realCallingPackage = - mService.mContext.getPackageManager().getNameForUid(mRealCallingUid); - if (realCallingPackage == null) { - realCallingPackage = "uid=" + mRealCallingUid; - } - Slog.wtf(TAG, "Without Android 15 BAL hardening this activity would be moved to the " - + "foreground. The activity is started by a PendingIntent. However, only the " - + "creator of the PendingIntent allows BAL while the sender does not allow BAL. " - + "realCallingPackage: " + realCallingPackage - + "; callingPackage: " + mRequest.callingPackage - + "; mTargetRootTask:" + mTargetRootTask - + "; mIntent: " + mIntent - + "; mTargetRootTask.getTopNonFinishingActivity: " - + mTargetRootTask.getTopNonFinishingActivity() - + "; mTargetRootTask.getRootActivity: " + mTargetRootTask.getRootActivity()); - } - private void recordTransientLaunchIfNeeded(ActivityRecord r) { if (r == null || !mTransientLaunch) return; final TransitionController controller = r.mTransitionController; @@ -2287,7 +2222,7 @@ class ActivityStarter { } if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart( - mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode, + mSourceRecord, r, newTask, mAvoidMoveToFront, targetTask, mLaunchFlags, mBalCode, mCallingUid, mRealCallingUid, mPreferredTaskDisplayArea)) { return START_ABORTED; } @@ -2635,7 +2570,7 @@ class ActivityStarter { mIsTaskCleared = false; mMovedToFront = false; mNoAnimation = false; - mCanMoveToFrontCode = MOVE_TO_FRONT_ALLOWED; + mAvoidMoveToFront = false; mFrozeTaskList = false; mTransientLaunch = false; mPriorAboveTask = null; @@ -2747,12 +2682,12 @@ class ActivityStarter { // The caller specifies that we'd like to be avoided to be moved to the // front, so be it! mDoResume = false; - mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY; + mAvoidMoveToFront = true; } } } else if (mOptions.getAvoidMoveToFront()) { mDoResume = false; - mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY; + mAvoidMoveToFront = true; } mTransientLaunch = mOptions.getTransientLaunch(); final KeyguardController kc = mSupervisor.getKeyguardController(); @@ -2762,7 +2697,7 @@ class ActivityStarter { if (mTransientLaunch && mDisplayLockAndOccluded && mService.getTransitionController().isShellTransitionsEnabled()) { mDoResume = false; - mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY; + mAvoidMoveToFront = true; } mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask()); @@ -2819,7 +2754,7 @@ class ActivityStarter { mNoAnimation = (mLaunchFlags & FLAG_ACTIVITY_NO_ANIMATION) != 0; if (mBalCode == BAL_BLOCK && !mService.isBackgroundActivityStartsEnabled()) { - mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY; + mAvoidMoveToFront = true; mDoResume = false; } } @@ -3050,7 +2985,7 @@ class ActivityStarter { differentTopTask = true; } - if (differentTopTask && !avoidMoveToFront()) { + if (differentTopTask && !mAvoidMoveToFront) { mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); // We really do want to push this one into the user's face, right now. if (mLaunchTaskBehind && mSourceRecord != null) { @@ -3094,9 +3029,6 @@ class ActivityStarter { } mOptions = null; } - if (differentTopTask) { - logPIOnlyCreatorAllowsBAL(); - } // Update the target's launch cookie and pending remote animation to those specified in the // options if set. if (mStartActivity.mLaunchCookie != null) { @@ -3137,7 +3069,7 @@ class ActivityStarter { } private void setNewTask(Task taskToAffiliate) { - final boolean toTop = !mLaunchTaskBehind && !avoidMoveToFront(); + final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront; final Task task = mTargetRootTask.reuseOrCreateTask( mStartActivity.info, mIntent, mVoiceSession, mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions); diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java index 35fa39dab900..d994a1904a14 100644 --- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java @@ -180,10 +180,7 @@ class AppCompatOrientationPolicy { return true; } - final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy - .getAppCompatCameraPolicy(mActivityRecord); - if (cameraPolicy != null - && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) { + if (AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) { Slog.w(TAG, "Ignoring orientation update to " + screenOrientationToString(requestedOrientation) + " due to camera compat treatment for " + mActivityRecord); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 119709e86551..6df01f4b328b 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -45,12 +45,11 @@ import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONL import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel; import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS; import static com.android.window.flags.Flags.balAdditionalStartModes; -import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg; import static com.android.window.flags.Flags.balImprovedMetrics; import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator; import static com.android.window.flags.Flags.balShowToastsBlocked; -import static com.android.window.flags.Flags.balStrictModeRo; import static com.android.window.flags.Flags.balStrictModeGracePeriod; +import static com.android.window.flags.Flags.balStrictModeRo; import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.util.Objects.requireNonNull; @@ -620,8 +619,6 @@ public class BackgroundActivityStartController { // features sb.append("; balRequireOptInByPendingIntentCreator: ") .append(balRequireOptInByPendingIntentCreator()); - sb.append("; balDontBringExistingBackgroundTaskStackToFg: ") - .append(balDontBringExistingBackgroundTaskStackToFg()); sb.append("]"); return sb.toString(); } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index f465c95addb7..4bcba13448e9 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -899,9 +899,7 @@ class InsetsPolicy { } @Override - public void notifyAnimationRunningStateChanged(boolean running, - @InsetsController.AnimationType int animationType, - @InsetsType int insetsTypes) { + public void notifyAnimationRunningStateChanged(boolean running) { mInsetsAnimationRunning = running; } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e761e024b3ca..883d8f95b612 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1680,26 +1680,27 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * Sets the specified orientation of this container. It percolates this change upward along the * hierarchy to let each level of the hierarchy a chance to respond to it. * - * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}. + * @param requestedOrientation the specified orientation. Needs to be one of + * {@link ScreenOrientation}. * @param requestingContainer the container which orientation request has changed. Mostly used * to ensure it gets correct configuration. * @return the resolved override orientation of this window container. */ @ScreenOrientation - int setOrientation(@ScreenOrientation int orientation, + int setOrientation(@ScreenOrientation int requestedOrientation, @Nullable WindowContainer requestingContainer) { - if (getOverrideOrientation() == orientation) { - return orientation; + if (getOverrideOrientation() == requestedOrientation) { + return requestedOrientation; } - setOverrideOrientation(orientation); + setOverrideOrientation(requestedOrientation); final WindowContainer parent = getParent(); if (parent == null) { - return orientation; + return requestedOrientation; } // The derived class can return a result that is different from the given orientation. - final int resolvedOrientation = getOverrideOrientation(); + final int actualOverrideOrientation = getOverrideOrientation(); if (getConfiguration().orientation != getRequestedConfigurationOrientation( - false /* forDisplay */, resolvedOrientation) + false /* forDisplay */, actualOverrideOrientation) // Update configuration directly only if the change won't be dispatched from // ancestor. This prevents from computing intermediate configuration when the // parent also needs to be updated from the ancestor. E.g. the app requests @@ -1707,12 +1708,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // the task can be updated to portrait first so the configuration can be // computed in a consistent environment. && (inMultiWindowMode() - || !handlesOrientationChangeFromDescendant(orientation))) { + || !handlesOrientationChangeFromDescendant(requestedOrientation))) { // Resolve the requested orientation. onConfigurationChanged(parent.getConfiguration()); } onDescendantOrientationChanged(requestingContainer); - return resolvedOrientation; + return actualOverrideOrientation; } @ScreenOrientation diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 7a8230f1f963..c77b1d9a7bcf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -41,7 +41,6 @@ import android.view.Display; import android.view.IInputFilter; import android.view.IRemoteAnimationFinishedCallback; import android.view.IWindow; -import android.view.InsetsController; import android.view.MagnificationSpec; import android.view.RemoteAnimationTarget; import android.view.Surface; @@ -470,24 +469,6 @@ public abstract class WindowManagerInternal { public abstract void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion); /** - * Set by the autofill service to observe changes in the ime animations. - * - * @param listener The callbacks to invoke. - */ - public abstract void setImeInsetsAnimationChangeListener( - @Nullable ImeInsetsAnimationChangeListener listener); - - /** Listener for changes in ime insets animation */ - public interface ImeInsetsAnimationChangeListener { - - /** Notify on start of animation */ - void onAnimationStart(@InsetsController.AnimationType int animationType, int userId); - - /** Notify on end of animation */ - void onAnimationEnd(@InsetsController.AnimationType int animationType, int userId); - } - - /** * Sets a callback for observing which windows are touchable for the purposes * of accessibility on specified display. * diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 28ea3b0bf6ba..3a1d652f82d4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.Manifest.permission.ACCESS_SURFACE_FLINGER; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INPUT_CONSUMER; @@ -278,7 +277,6 @@ import android.view.InputApplicationHandle; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputWindowHandle; -import android.view.InsetsController; import android.view.InsetsFrameProvider; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -795,9 +793,6 @@ public class WindowManagerService extends IWindowManager.Stub final TrustedPresentationListenerController mTrustedPresentationListenerController = new TrustedPresentationListenerController(); - private WindowManagerInternal.ImeInsetsAnimationChangeListener - mImeInsetsAnimationChangeListener; - @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -8627,14 +8622,6 @@ public class WindowManagerService extends IWindowManager.Stub // WMS.takeAssistScreenshot takes care of the locking. return WindowManagerService.this.takeAssistScreenshot(windowTypesToExclude); } - - @Override - public void setImeInsetsAnimationChangeListener( - @Nullable WindowManagerInternal.ImeInsetsAnimationChangeListener listener) { - synchronized (mGlobalLock) { - mImeInsetsAnimationChangeListener = listener; - } - } } private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy { @@ -10192,24 +10179,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public void notifyImeInsetsAnimationStateChanged( - boolean running, @InsetsController.AnimationType int animationType) { - if (improveFillDialogAconfig()) { - synchronized (mGlobalLock) { - if (mImeInsetsAnimationChangeListener == null) { - return; - } - if (running) { - mImeInsetsAnimationChangeListener.onAnimationStart( - animationType, mCurrentUserId); - } else { - mImeInsetsAnimationChangeListener.onAnimationEnd(animationType, mCurrentUserId); - } - } - } - } - boolean getDisableSecureWindows() { return mDisableSecureWindows; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a1755e4d9d3b..060f2e803ec9 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -756,40 +756,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub t.getTaskFragmentOrganizer()); } } - // Queue-up bounds-change transactions for tasks which are now organized. Do - // this after hierarchy ops so we have the final organized state. - entries = t.getChanges().entrySet().iterator(); - while (entries.hasNext()) { - final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); - final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); - if (wc == null || !wc.isAttached()) { - Slog.e(TAG, "Attempt to operate on detached container: " + wc); - continue; - } - final Task task = wc.asTask(); - final Rect surfaceBounds = entry.getValue().getBoundsChangeSurfaceBounds(); - if (task == null || !task.isAttached() || surfaceBounds == null) { - continue; - } - if (!task.isOrganized()) { - final Task parent = task.getParent() != null ? task.getParent().asTask() : null; - // Also allow direct children of created-by-organizer tasks to be - // controlled. In the future, these will become organized anyways. - if (parent == null || !parent.mCreatedByOrganizer) { - throw new IllegalArgumentException( - "Can't manipulate non-organized task surface " + task); - } - } - final SurfaceControl.Transaction sft = new SurfaceControl.Transaction(); - final SurfaceControl sc = task.getSurfaceControl(); - sft.setPosition(sc, surfaceBounds.left, surfaceBounds.top); - if (surfaceBounds.isEmpty()) { - sft.setWindowCrop(sc, null); - } else { - sft.setWindowCrop(sc, surfaceBounds.width(), surfaceBounds.height()); - } - task.setMainWindowSizeChangeTransaction(sft); - } if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); mService.mTaskSupervisor.endDeferResume(); diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index 073ee31ddd60..195c65d6ec45 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -23,6 +23,7 @@ import static com.android.internal.util.Preconditions.checkCallAuthorization; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; @@ -51,7 +52,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.pm.UserManagerInternal; import java.io.FileDescriptor; @@ -62,26 +62,28 @@ import java.util.List; public class SupervisionService extends ISupervisionManager.Stub { private static final String LOG_TAG = "SupervisionService"; - private final Context mContext; - // TODO(b/362756788): Does this need to be a LockGuard lock? private final Object mLockDoNoUseDirectly = new Object(); @GuardedBy("getLockObject()") private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); - private final DevicePolicyManagerInternal mDpmInternal; - private final PackageManager mPackageManager; - private final UserManagerInternal mUserManagerInternal; + private final Context mContext; + private final Injector mInjector; + final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); public SupervisionService(Context context) { mContext = context.createAttributionContext(LOG_TAG); - mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); - mPackageManager = context.getPackageManager(); - mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); - mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener()); + mInjector = new Injector(context); + mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); } + /** + * Returns whether supervision is enabled for the given user. + * + * <p>Supervision is automatically enabled when the supervision app becomes the profile owner or + * explicitly enabled via an internal call to {@link #setSupervisionEnabledForUser}. + */ @Override public boolean isSupervisionEnabledForUser(@UserIdInt int userId) { if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { @@ -92,6 +94,20 @@ public class SupervisionService extends ISupervisionManager.Stub { } } + /** + * Returns the package name of the active supervision app or null if supervision is disabled. + */ + @Override + @Nullable + public String getActiveSupervisionAppPackage(@UserIdInt int userId) { + if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { + enforcePermission(INTERACT_ACROSS_USERS); + } + synchronized (getLockObject()) { + return getUserDataLocked(userId).supervisionAppPackage; + } + } + @Override public void onShellCommand( @Nullable FileDescriptor in, @@ -114,7 +130,7 @@ public class SupervisionService extends ISupervisionManager.Stub { pw.println("SupervisionService state:"); pw.increaseIndent(); - List<UserInfo> users = mUserManagerInternal.getUsers(false); + List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(false); synchronized (getLockObject()) { for (var user : users) { getUserDataLocked(user.id).dump(pw); @@ -140,35 +156,54 @@ public class SupervisionService extends ISupervisionManager.Stub { return data; } - void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { + /** + * Sets supervision as enabled or disabled for the given user and, in case supervision is being + * enabled, the package of the active supervision app. + */ + private void setSupervisionEnabledForUser( + @UserIdInt int userId, boolean enabled, @Nullable String supervisionAppPackage) { synchronized (getLockObject()) { - getUserDataLocked(userId).supervisionEnabled = enabled; + SupervisionUserData data = getUserDataLocked(userId); + data.supervisionEnabled = enabled; + data.supervisionAppPackage = enabled ? supervisionAppPackage : null; } } - /** Ensures that supervision is enabled when supervision app is the profile owner. */ + /** Ensures that supervision is enabled when the supervision app is the profile owner. */ private void syncStateWithDevicePolicyManager(@UserIdInt int userId) { - if (isProfileOwner(userId)) { - setSupervisionEnabledForUser(userId, true); + final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal(); + final ComponentName po = + dpmInternal != null ? dpmInternal.getProfileOwnerAsUser(userId) : null; + + if (po != null && po.getPackageName().equals(getSystemSupervisionPackage())) { + setSupervisionEnabledForUser(userId, true, po.getPackageName()); + } else if (po != null && po.equals(getSupervisionProfileOwnerComponent())) { + // TODO(b/392071637): Consider not enabling supervision in case profile owner is given + // to the legacy supervision profile owner component. + setSupervisionEnabledForUser(userId, true, po.getPackageName()); } else { // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner. // This might only be possible after introducing specific and public APIs to enable - // supervision. - setSupervisionEnabledForUser(userId, false); + // and disable supervision. + setSupervisionEnabledForUser(userId, false, /* supervisionAppPackage= */ null); } } - /** Returns whether the supervision app has profile owner status. */ - private boolean isProfileOwner(@UserIdInt int userId) { - ComponentName profileOwner = - mDpmInternal != null ? mDpmInternal.getProfileOwnerAsUser(userId) : null; - return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName()); + /** + * Returns the {@link ComponentName} of the supervision profile owner component. + * + * <p>This component is used to give GMS Kids Module permission to supervise the device and may + * still be active during the transition to the {@code SYSTEM_SUPERVISION} role. + */ + private ComponentName getSupervisionProfileOwnerComponent() { + return ComponentName.unflattenFromString( + mContext.getResources() + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)); } - /** Returns whether the given package name belongs to the supervision role holder. */ - private boolean isSupervisionAppPackage(String packageName) { - return packageName.equals( - mContext.getResources().getString(R.string.config_systemSupervision)); + /** Returns the package assigned to the {@code SYSTEM_SUPERVISION} role. */ + private String getSystemSupervisionPackage() { + return mContext.getResources().getString(R.string.config_systemSupervision); } /** Enforces that the caller has the given permission. */ @@ -177,6 +212,41 @@ public class SupervisionService extends ISupervisionManager.Stub { mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED); } + /** Provides local services in a lazy manner. */ + static class Injector { + private final Context mContext; + private DevicePolicyManagerInternal mDpmInternal; + private PackageManager mPackageManager; + private UserManagerInternal mUserManagerInternal; + + Injector(Context context) { + mContext = context; + } + + @Nullable + DevicePolicyManagerInternal getDpmInternal() { + if (mDpmInternal == null) { + mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); + } + return mDpmInternal; + } + + PackageManager getPackageManager() { + if (mPackageManager == null) { + mPackageManager = mContext.getPackageManager(); + } + return mPackageManager; + } + + UserManagerInternal getUserManagerInternal() { + if (mUserManagerInternal == null) { + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + } + return mUserManagerInternal; + } + } + + /** Publishes local and binder services and allows the service to act during initialization. */ public static class Lifecycle extends SystemService { private final SupervisionService mSupervisionService; @@ -201,6 +271,7 @@ public class SupervisionService extends ISupervisionManager.Stub { } @VisibleForTesting + @SuppressLint("MissingPermission") // not needed for a service void registerProfileOwnerListener() { IntentFilter poIntentFilter = new IntentFilter(); poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED); @@ -209,7 +280,7 @@ public class SupervisionService extends ISupervisionManager.Stub { .registerReceiverForAllUsers( new ProfileOwnerBroadcastReceiver(), poIntentFilter, - /* brodcastPermission= */ null, + /* broadcastPermission= */ null, /* scheduler= */ null); } @@ -228,19 +299,22 @@ public class SupervisionService extends ISupervisionManager.Stub { } } - final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); - + /** Implementation of the local service, API used by other services. */ private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal { @Override public boolean isActiveSupervisionApp(int uid) { - String[] packages = mPackageManager.getPackagesForUid(uid); - if (packages == null) { + int userId = UserHandle.getUserId(uid); + String supervisionAppPackage = getActiveSupervisionAppPackage(userId); + if (supervisionAppPackage == null) { return false; } - for (var packageName : packages) { - if (SupervisionService.this.isSupervisionAppPackage(packageName)) { - int userId = UserHandle.getUserId(uid); - return SupervisionService.this.isSupervisionEnabledForUser(userId); + + String[] packages = mInjector.getPackageManager().getPackagesForUid(uid); + if (packages != null) { + for (var packageName : packages) { + if (supervisionAppPackage.equals(packageName)) { + return true; + } } } return false; @@ -253,7 +327,8 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { - SupervisionService.this.setSupervisionEnabledForUser(userId, enabled); + SupervisionService.this.setSupervisionEnabledForUser( + userId, enabled, getSystemSupervisionPackage()); } @Override @@ -274,6 +349,7 @@ public class SupervisionService extends ISupervisionManager.Stub { } } + /** Deletes user data when the user gets removed. */ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener { @Override public void onUserRemoved(UserInfo user) { diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java index 2adaae3943f1..976642bd563d 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java @@ -32,16 +32,18 @@ public class SupervisionServiceShellCommand extends ShellCommand { return handleDefaultCommands(null); } switch (cmd) { - case "enable": return setEnabled(true); - case "disable": return setEnabled(false); - default: return handleDefaultCommands(cmd); + case "enable": + return setEnabled(true); + case "disable": + return setEnabled(false); + default: + return handleDefaultCommands(cmd); } } private int setEnabled(boolean enabled) { - final var pw = getOutPrintWriter(); final var userId = UserHandle.parseUserArg(getNextArgRequired()); - mService.setSupervisionEnabledForUser(userId, enabled); + mService.mInternal.setSupervisionEnabledForUser(userId, enabled); return 0; } diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java index 1dd48f581bf4..06acb91509a1 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java @@ -26,6 +26,7 @@ import android.util.IndentingPrintWriter; public class SupervisionUserData { public final @UserIdInt int userId; public boolean supervisionEnabled; + @Nullable public String supervisionAppPackage; public boolean supervisionLockScreenEnabled; @Nullable public PersistableBundle supervisionLockScreenOptions; @@ -38,6 +39,7 @@ public class SupervisionUserData { pw.println("User " + userId + ":"); pw.increaseIndent(); pw.println("supervisionEnabled: " + supervisionEnabled); + pw.println("supervisionAppPackage: " + supervisionAppPackage); pw.println("supervisionLockScreenEnabled: " + supervisionLockScreenEnabled); pw.println("supervisionLockScreenOptions: " + supervisionLockScreenOptions); pw.decreaseIndent(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt index 01061f16c279..d9224eaf66ca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt @@ -29,6 +29,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever private val TEST_PLUGIN_TYPE = PluginType(Int::class.java, "test_type") +private val DISPLAY_ID = "display_id" @SmallTest class PluginManagerTest { @@ -62,18 +63,18 @@ class PluginManagerTest { fun testSubscribe() { val pluginManager = createPluginManager() - pluginManager.subscribe(TEST_PLUGIN_TYPE, mockListener) + pluginManager.subscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) - verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, mockListener) + verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) } @Test fun testUnsubscribe() { val pluginManager = createPluginManager() - pluginManager.unsubscribe(TEST_PLUGIN_TYPE, mockListener) + pluginManager.unsubscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) - verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, mockListener) + verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) } private fun createPluginManager(enabled: Boolean = true): PluginManager { diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt index 218e34134e93..8eb3e9fbf9b0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt @@ -23,6 +23,8 @@ import org.junit.Test private val TEST_PLUGIN_TYPE1 = PluginType(String::class.java, "test_type1") private val TEST_PLUGIN_TYPE2 = PluginType(String::class.java, "test_type2") +private val DISPLAY_ID_1 = "display_1" +private val DISPLAY_ID_2 = "display_2" @SmallTest class PluginStorageTest { @@ -33,9 +35,9 @@ class PluginStorageTest { fun testUpdateValue() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -44,9 +46,9 @@ class PluginStorageTest { fun testAddListener() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -55,10 +57,10 @@ class PluginStorageTest { fun testRemoveListener() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) - storage.removeListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) + storage.removeListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener.receivedValue).isNull() } @@ -68,10 +70,10 @@ class PluginStorageTest { val type1Value = "value1" val type2Value = "value2" val testChangeListener = TestPluginChangeListener<String>() - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) - storage.updateValue(TEST_PLUGIN_TYPE2, type2Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, type2Value) - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -81,15 +83,62 @@ class PluginStorageTest { val type1Value = "value1" val testChangeListener1 = TestPluginChangeListener<String>() val testChangeListener2 = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener1) - storage.addListener(TEST_PLUGIN_TYPE2, testChangeListener2) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, testChangeListener2) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) assertThat(testChangeListener2.receivedValue).isNull() } + @Test + fun testUpdateGlobal_noDisplaySpecificValue() { + val type1Value = "value1" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1Value) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1Value) + } + + @Test + fun testUpdateGlobal_withDisplaySpecificValue() { + val type1Value = "value1" + val type1GlobalValue = "value1Global" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue) + } + + @Test + fun testUpdateGlobal_withDisplaySpecificValueRemoved() { + val type1Value = "value1" + val type1GlobalValue = "value1Global" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, null) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1GlobalValue) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue) + } + private class TestPluginChangeListener<T> : PluginChangeListener<T> { var receivedValue: T? = null diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index 89b48bad2358..d6349fc0651f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -16,6 +16,9 @@ package com.android.server.am; +import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; import static android.os.Process.INVALID_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -27,9 +30,12 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANC import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED; +import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.cancelReasonToString; +import static com.android.window.flags.Flags.balClearAllowlistDuration; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -39,9 +45,11 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; +import android.app.BackgroundStartPrivileges; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.IPackageManager; +import android.os.Binder; import android.os.Looper; import android.os.UserHandle; @@ -179,6 +187,41 @@ public class PendingIntentControllerTest { } } + @Test + public void testClearAllowBgActivityStartsClearsToken() { + final PendingIntentRecord pir = createPendingIntentRecord(0); + Binder token = new Binder(); + pir.setAllowBgActivityStarts(token, FLAG_ACTIVITY_SENDER); + assertEquals(BackgroundStartPrivileges.allowBackgroundActivityStarts(token), + pir.getBackgroundStartPrivilegesForActivitySender(token)); + pir.clearAllowBgActivityStarts(token); + assertEquals(BackgroundStartPrivileges.NONE, + pir.getBackgroundStartPrivilegesForActivitySender(token)); + } + + @Test + public void testClearAllowBgActivityStartsClearsDuration() { + final PendingIntentRecord pir = createPendingIntentRecord(0); + Binder token = new Binder(); + pir.setAllowlistDurationLocked(token, 1000, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, + "NotificationManagerService"); + PendingIntentRecord.TempAllowListDuration allowlistDurationLocked = + pir.getAllowlistDurationLocked(token); + assertEquals(1000, allowlistDurationLocked.duration); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + allowlistDurationLocked.type); + pir.clearAllowBgActivityStarts(token); + PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear = + pir.getAllowlistDurationLocked(token); + assertNotNull(allowlistDurationLockedAfterClear); + assertEquals(1000, allowlistDurationLockedAfterClear.duration); + assertEquals(balClearAllowlistDuration() + ? TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED + : TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + allowlistDurationLocked.type); + } + private void assertCancelReason(int expectedReason, int actualReason) { final String errMsg = "Expected: " + cancelReasonToString(expectedReason) + "; Actual: " + cancelReasonToString(actualReason); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index d427c9d9ee37..e94ef5bb4871 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -39,6 +39,8 @@ import android.os.Handler; import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseLongArray; @@ -49,6 +51,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; +import com.android.server.power.optimization.Flags; import com.android.server.power.stats.processor.MultiStatePowerAttributor; import org.junit.Before; @@ -59,6 +62,7 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) @@ -68,11 +72,14 @@ public class BatteryUsageStatsProviderTest { .setProvideMainThread(true) .build(); + @Rule(order = 1) + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; private static final long MINUTE_IN_MS = 60 * 1000; private static final double PRECISION = 0.00001; - @Rule(order = 1) + @Rule(order = 2) public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345) .createTempDirectory() @@ -868,4 +875,62 @@ public class BatteryUsageStatsProviderTest { stats.close(); } + + @Test + @EnableFlags(Flags.FLAG_EXTENDED_BATTERY_HISTORY_CONTINUOUS_COLLECTION_ENABLED) + public void testIncludeSubsetOfHistory() throws IOException { + MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + batteryStats.getHistory().setMaxHistoryBufferSize(100); + synchronized (batteryStats) { + batteryStats.setRecordAllHistoryLocked(true); + } + batteryStats.forceRecordAllHistory(); + batteryStats.setNoAutoReset(true); + + long lastIncludedEventTimestamp = 0; + String tag = "work work work work work work work work work work work work work work work"; + for (int i = 1; i < 50; i++) { + mStatsRule.advanceTime(TimeUnit.MINUTES.toMillis(9)); + synchronized (batteryStats) { + batteryStats.noteJobStartLocked(tag, 42); + } + mStatsRule.advanceTime(TimeUnit.MINUTES.toMillis(1)); + synchronized (batteryStats) { + batteryStats.noteJobFinishLocked(tag, 42, 0); + } + lastIncludedEventTimestamp = mMonotonicClock.monotonicTime(); + } + + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, + mock(PowerAttributor.class), mStatsRule.getPowerProfile(), + mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock, + mMonotonicClock); + + BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .includeBatteryHistory() + .setPreferredHistoryDurationMs(TimeUnit.MINUTES.toMillis(20)) + .build(); + final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query); + Parcel parcel = Parcel.obtain(); + stats.writeToParcel(parcel, 0); + stats.close(); + + parcel.setDataPosition(0); + BatteryUsageStats actual = BatteryUsageStats.CREATOR.createFromParcel(parcel); + + long firstIncludedEventTimestamp = 0; + try (BatteryStatsHistoryIterator it = actual.iterateBatteryStatsHistory()) { + BatteryStats.HistoryItem item; + while ((item = it.next()) != null) { + if (item.eventCode == BatteryStats.HistoryItem.EVENT_JOB_START) { + firstIncludedEventTimestamp = item.time; + break; + } + } + } + actual.close(); + + assertThat(firstIncludedEventTimestamp) + .isAtLeast(lastIncludedEventTimestamp - TimeUnit.MINUTES.toMillis(30)); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 4ef602f1a64c..3511ae12497a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -58,9 +58,9 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.os.test.TestLooper; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.testing.DexmakerShareClassLoaderRule; @@ -173,6 +173,8 @@ public class MagnificationControllerTest { @Mock private Scroller mMockScroller; + private TestLooper mTestLooper; + // To mock package-private class @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = @@ -199,14 +201,16 @@ public class MagnificationControllerTest { mMockResolver = new MockContentResolver(); mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - Looper looper = InstrumentationRegistry.getContext().getMainLooper(); - // Pretending ID of the Thread associated with looper as main thread ID in controller - when(mContext.getMainLooper()).thenReturn(looper); + mTestLooper = new TestLooper(); + when(mContext.getMainLooper()).thenReturn( + InstrumentationRegistry.getContext().getMainLooper()); when(mContext.getContentResolver()).thenReturn(mMockResolver); when(mContext.getPackageManager()).thenReturn(mPackageManager); Settings.Secure.putFloatForUser(mMockResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE, CURRENT_USER_ID); + Settings.Secure.putFloatForUser(mMockResolver, Settings.Secure.KEY_REPEAT_ENABLED, 1, + CURRENT_USER_ID); mScaleProvider = spy(new MagnificationScaleProvider(mContext)); when(mControllerCtx.getContext()).thenReturn(mContext); @@ -251,7 +255,7 @@ public class MagnificationControllerTest { mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext, mScreenMagnificationController, mMagnificationConnectionManager, mScaleProvider, - ConcurrentUtils.DIRECT_EXECUTOR)); + ConcurrentUtils.DIRECT_EXECUTOR, mTestLooper.getLooper())); mMagnificationController.setMagnificationCapabilities( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); @@ -261,6 +265,7 @@ public class MagnificationControllerTest { @After public void tearDown() { + mTestLooper.dispatchAll(); FakeSettingsProvider.clearSettingsProvider(); } @@ -880,6 +885,69 @@ public class MagnificationControllerTest { } @Test + public void magnificationCallbacks_panMagnificationContinuous() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false); + reset(mScreenMagnificationController); + + DisplayMetrics metrics = new DisplayMetrics(); + mDisplay.getMetrics(metrics); + float expectedStep = 27 * metrics.density; + + float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + + // Start moving right using keyboard callbacks. + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_RIGHT); + + float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(currentCenterX).isLessThan(newCenterX); + expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep); + expect.that(currentCenterY).isEqualTo(newCenterY); + + currentCenterX = newCenterX; + currentCenterY = newCenterY; + + // Wait for the initial delay to occur. + advanceTime(MagnificationController.INITIAL_KEYBOARD_REPEAT_INTERVAL_MS + 1); + + // It should have moved again after the handler was triggered. + newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(currentCenterX).isLessThan(newCenterX); + expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep); + expect.that(currentCenterY).isEqualTo(newCenterY); + currentCenterX = newCenterX; + currentCenterY = newCenterY; + + // Wait for repeat delay to occur. + advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1); + + // It should have moved a third time. + newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(currentCenterX).isLessThan(newCenterX); + expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep); + expect.that(currentCenterY).isEqualTo(newCenterY); + currentCenterX = newCenterX; + currentCenterY = newCenterY; + + // Stop magnification pan. + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_RIGHT); + + // It should not move again, even after the appropriate delay. + advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1); + + newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(newCenterX).isEqualTo(currentCenterX); + expect.that(newCenterY).isEqualTo(currentCenterY); + } + + @Test public void enableWindowMode_notifyMagnificationChanged() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); @@ -1196,7 +1264,8 @@ public class MagnificationControllerTest { assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, lastActivatedMode); } - @Test public void activateFullScreenMagnification_triggerCallback() throws RemoteException { + @Test + public void activateFullScreenMagnification_triggerCallback() throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); verify(mMagnificationController).onFullScreenMagnificationActivationState( eq(TEST_DISPLAY), eq(true)); @@ -1573,8 +1642,8 @@ public class MagnificationControllerTest { float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); - // Move right. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + // Move right using keyboard callbacks. + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_RIGHT); float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); @@ -1582,11 +1651,13 @@ public class MagnificationControllerTest { expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep); expect.that(currentCenterY).isEqualTo(newCenterY); + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_RIGHT); currentCenterX = newCenterX; currentCenterY = newCenterY; // Move left. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_LEFT); newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); @@ -1594,11 +1665,13 @@ public class MagnificationControllerTest { expect.that(currentCenterX - newCenterX).isWithin(0.01f).of(expectedStep); expect.that(currentCenterY).isEqualTo(newCenterY); + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_LEFT); currentCenterX = newCenterX; currentCenterY = newCenterY; // Move down. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_DOWN); newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); @@ -1606,17 +1679,22 @@ public class MagnificationControllerTest { expect.that(currentCenterY).isLessThan(newCenterY); expect.that(newCenterY - currentCenterY).isWithin(0.1f).of(expectedStep); + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_DOWN); currentCenterX = newCenterX; currentCenterY = newCenterY; // Move up. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_UP); newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); expect.that(currentCenterX).isEqualTo(newCenterX); expect.that(currentCenterY).isGreaterThan(newCenterY); expect.that(currentCenterY - newCenterY).isWithin(0.01f).of(expectedStep); + + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_UP); } private void testWindowMagnificationPanWithStepSize(float expectedStepDip) @@ -1626,28 +1704,41 @@ public class MagnificationControllerTest { final float expectedStep = expectedStepDip * metrics.density; // Move right. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_RIGHT); verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), floatThat(step -> Math.abs(step - expectedStep) < 0.0001), eq(0.0f)); + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_RIGHT); // Move left. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_LEFT); verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), floatThat(step -> Math.abs(expectedStep - step) < 0.0001), eq(0.0f)); + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_LEFT); // Move down. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_DOWN); verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001)); + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_DOWN); // Move up. - mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + mMagnificationController.onPanMagnificationStart(TEST_DISPLAY, MagnificationController.PAN_DIRECTION_UP); verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001)); + mMagnificationController.onPanMagnificationStop(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_UP); + } + + private void advanceTime(long timeMs) { + mTestLooper.moveTimeForward(timeMs); + mTestLooper.dispatchAll(); } private static class WindowMagnificationMgrCallbackDelegate implements 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 3ced56a04138..a58a9cd2a28f 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 @@ -34,7 +34,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -53,15 +52,11 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; -import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; import android.app.AppOpsManager; -import android.app.Instrumentation; import android.app.KeyguardManager; -import android.app.role.RoleManager; -import android.companion.AssociationRequest; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -76,11 +71,9 @@ import android.media.projection.StopReason; import android.os.Binder; import android.os.IBinder; import android.os.Looper; -import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; 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; @@ -99,7 +92,6 @@ import com.android.server.testutils.OffsettableClock; import com.android.server.wm.WindowManagerInternal; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -110,7 +102,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -292,8 +283,6 @@ public class MediaProjectionManagerServiceTest { assertThat(stoppedCallback2).isFalse(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked() throws Exception { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); @@ -308,8 +297,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_packageAllowlisted() throws NameNotFoundException { @@ -325,8 +312,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_AppOpMediaProjection() throws NameNotFoundException { @@ -347,50 +332,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) - @Test - public void testCreateProjection_keyguardLocked_RoleHeld() { - runWithRole( - AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - () -> { - try { - mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED; - doReturn(mAppInfo) - .when(mPackageManager) - .getApplicationInfoAsUser( - anyString(), - any(ApplicationInfoFlags.class), - any(UserHandle.class)); - MediaProjectionManagerService.MediaProjection projection = - mService.createProjectionInternal( - Process.myUid(), - mContext.getPackageName(), - TYPE_MIRRORING, - /* isPermanentGrant= */ false, - UserHandle.CURRENT, - DEFAULT_DISPLAY); - 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. - assertWithMessage("Failed to run projection") - .that(mService.getActiveProjectionInfo()) - .isNotNull(); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - }); - } - - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_screenshareProtectionsDisabled() throws NameNotFoundException { @@ -416,8 +357,6 @@ public class MediaProjectionManagerServiceTest { } } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_noDisplayCreated() throws NameNotFoundException { @@ -509,8 +448,6 @@ public class MediaProjectionManagerServiceTest { assertThat(secondProjection).isNotEqualTo(projection); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testReuseProjection_keyguardNotLocked_startConsentDialog() throws NameNotFoundException { @@ -527,8 +464,6 @@ public class MediaProjectionManagerServiceTest { verify(mContext).startActivityAsUser(any(), any()); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); @@ -1302,48 +1237,6 @@ public class MediaProjectionManagerServiceTest { return mService.getProjectionInternal(UID, PACKAGE_NAME); } - /** - * Run the provided block giving the current context's package the provided role. - */ - @SuppressWarnings("SameParameterValue") - private void runWithRole(String role, Runnable block) { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - String packageName = mContext.getPackageName(); - UserHandle user = instrumentation.getTargetContext().getUser(); - RoleManager roleManager = Objects.requireNonNull( - mContext.getSystemService(RoleManager.class)); - try { - CountDownLatch latch = new CountDownLatch(1); - instrumentation.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.MANAGE_ROLE_HOLDERS, - Manifest.permission.BYPASS_ROLE_QUALIFICATION); - - roleManager.setBypassingRoleQualification(true); - roleManager.addRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), success -> { - if (success) { - latch.countDown(); - } else { - Assert.fail("Couldn't set role for test (failure) " + role); - } - }); - assertWithMessage("Couldn't set role for test (timeout) : " + role) - .that(latch.await(1, TimeUnit.SECONDS)).isTrue(); - block.run(); - - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - roleManager.removeRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), (aBool) -> {}); - roleManager.setBypassingRoleQualification(false); - instrumentation.getUiAutomation() - .dropShellPermissionIdentity(); - } - } - private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub { CountDownLatch mLatch = new CountDownLatch(1); @Override diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java index 379079a0018c..10ac0495d69a 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java @@ -22,7 +22,6 @@ import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_ import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -37,13 +36,10 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; -import android.app.Instrumentation; import android.app.KeyguardManager; -import android.app.role.RoleManager; import android.companion.AssociationRequest; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -69,7 +65,6 @@ import com.android.server.SystemConfig; import com.android.server.wm.WindowManagerInternal; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -79,9 +74,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.List; import java.util.function.Consumer; /** @@ -123,6 +116,8 @@ public class MediaProjectionStopControllerTest { private KeyguardManager mKeyguardManager; @Mock private TelecomManager mTelecomManager; + @Mock + private MediaProjectionStopController.RoleHolderProvider mRoleManager; private AppOpsManager mAppOpsManager; @Mock @@ -145,7 +140,7 @@ public class MediaProjectionStopControllerTest { mContext.addMockSystemService(TelecomManager.class, mTelecomManager); mContext.setMockPackageManager(mPackageManager); - mStopController = new MediaProjectionStopController(mContext, mStopConsumer); + mStopController = new MediaProjectionStopController(mContext, mStopConsumer, mRoleManager); mService = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); @@ -170,8 +165,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testMediaProjectionNotRestricted() throws Exception { when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); @@ -180,8 +173,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testMediaProjectionRestricted() throws Exception { MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(); mediaProjection.notifyVirtualDisplayCreated(1); @@ -239,21 +230,13 @@ public class MediaProjectionStopControllerTest { @Test public void testExemptFromStoppingHasAppStreamingRole() throws Exception { - runWithRole( - AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - () -> { - try { - MediaProjectionManagerService.MediaProjection mediaProjection = - createMediaProjection(); - doReturn(PackageManager.PERMISSION_DENIED).when( - mPackageManager).checkPermission( - RECORD_SENSITIVE_CONTENT, mediaProjection.packageName); - assertThat(mStopController.isExemptFromStopping(mediaProjection, - MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(); + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + RECORD_SENSITIVE_CONTENT, mediaProjection.packageName); + doReturn(List.of(mediaProjection.packageName)).when(mRoleManager).getRoleHoldersAsUser( + eq(AssociationRequest.DEVICE_PROFILE_APP_STREAMING), any(UserHandle.class)); + assertThat(mStopController.isExemptFromStopping(mediaProjection, + MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue(); } @Test @@ -316,8 +299,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_unlocked() { mStopController.onKeyguardLockedStateChanged(false); @@ -325,8 +306,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_locked() { mStopController.onKeyguardLockedStateChanged(true); @@ -438,47 +417,4 @@ public class MediaProjectionStopControllerTest { MediaProjectionManager.TYPE_SCREEN_CAPTURE, false, mContext.getUser(), INVALID_DISPLAY); } - - /** - * Run the provided block giving the current context's package the provided role. - */ - @SuppressWarnings("SameParameterValue") - private void runWithRole(String role, Runnable block) { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - String packageName = mContext.getPackageName(); - UserHandle user = instrumentation.getTargetContext().getUser(); - RoleManager roleManager = Objects.requireNonNull( - mContext.getSystemService(RoleManager.class)); - try { - CountDownLatch latch = new CountDownLatch(1); - instrumentation.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.MANAGE_ROLE_HOLDERS, - Manifest.permission.BYPASS_ROLE_QUALIFICATION); - - roleManager.setBypassingRoleQualification(true); - roleManager.addRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), success -> { - if (success) { - latch.countDown(); - } else { - Assert.fail("Couldn't set role for test (failure) " + role); - } - }); - assertWithMessage("Couldn't set role for test (timeout) : " + role) - .that(latch.await(1, TimeUnit.SECONDS)).isTrue(); - block.run(); - - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - roleManager.removeRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), (aBool) -> { - }); - roleManager.setBypassingRoleQualification(false); - instrumentation.getUiAutomation() - .dropShellPermissionIdentity(); - } - } } diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt index 5862ac65eba9..af50effb7c8e 100644 --- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt @@ -92,6 +92,21 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(systemSupervisionPackage) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM) + fun onUserStarting_legacyProfileOwnerComponent_enablesSupervision() { + whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID)) + .thenReturn(supervisionProfileOwnerComponent) + + simulateUserStarting(USER_ID) + + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(supervisionProfileOwnerComponent.packageName) } @Test @@ -103,6 +118,7 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID, preCreated = true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test @@ -114,6 +130,7 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test @@ -125,6 +142,21 @@ class SupervisionServiceTest { broadcastProfileOwnerChanged(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(systemSupervisionPackage) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM) + fun profileOwnerChanged_legacyProfileOwnerComponent_enablesSupervision() { + whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID)) + .thenReturn(supervisionProfileOwnerComponent) + + broadcastProfileOwnerChanged(USER_ID) + + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(supervisionProfileOwnerComponent.packageName) } @Test @@ -136,13 +168,14 @@ class SupervisionServiceTest { broadcastProfileOwnerChanged(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() { whenever(mockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(arrayOf(systemSupervisionPackage)) - service.setSupervisionEnabledForUser(USER_ID, true) + service.mInternal.setSupervisionEnabledForUser(USER_ID, true) assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue() } @@ -151,7 +184,7 @@ class SupervisionServiceTest { fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() { whenever(mockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(arrayOf(systemSupervisionPackage)) - service.setSupervisionEnabledForUser(USER_ID, false) + service.mInternal.setSupervisionEnabledForUser(USER_ID, false) assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse() } @@ -167,15 +200,15 @@ class SupervisionServiceTest { fun setSupervisionEnabledForUser() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() - service.setSupervisionEnabledForUser(USER_ID, true) + service.mInternal.setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() - service.setSupervisionEnabledForUser(USER_ID, false) + service.mInternal.setSupervisionEnabledForUser(USER_ID, false) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() } @Test - fun supervisionEnabledForUser_internal() { + fun setSupervisionEnabledForUser_internal() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() service.mInternal.setSupervisionEnabledForUser(USER_ID, true) @@ -205,6 +238,13 @@ class SupervisionServiceTest { private val systemSupervisionPackage: String get() = context.getResources().getString(R.string.config_systemSupervision) + private val supervisionProfileOwnerComponent: ComponentName + get() = + context + .getResources() + .getString(R.string.config_defaultSupervisionProfileOwnerComponent) + .let(ComponentName::unflattenFromString)!! + private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) { val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0) userInfo.preCreated = preCreated diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index a63a38da3740..0eb20eb22380 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -20,6 +20,7 @@ android_test { ], static_libs: [ + "compatibility-device-util-axt-minus-dexmaker", "frameworks-base-testutils", "services.accessibility", "services.core", diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 4315254f68a9..69f17757b367 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -45,6 +45,8 @@ <provider android:name=".DummyProvider" android:authorities="com.android.services.uitests" /> + <activity android:name="android.app.ExampleActivity" /> + </application> <instrumentation diff --git a/services/tests/uiservicestests/src/android/app/ExampleActivity.java b/services/tests/uiservicestests/src/android/app/ExampleActivity.java new file mode 100644 index 000000000000..58395e4d75e1 --- /dev/null +++ b/services/tests/uiservicestests/src/android/app/ExampleActivity.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +public class ExampleActivity extends Activity { +} diff --git a/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java new file mode 100644 index 000000000000..779fa1aa2f72 --- /dev/null +++ b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; +import static android.app.NotificationSystemUtil.runAsSystemUi; +import static android.app.NotificationSystemUtil.toggleNotificationPolicyAccess; +import static android.service.notification.Condition.STATE_FALSE; +import static android.service.notification.Condition.STATE_TRUE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ComponentName; +import android.content.Context; +import android.net.Uri; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.service.notification.Condition; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Map; + +@RunWith(AndroidJUnit4.class) +public class NotificationManagerZenTest { + + private Context mContext; + private NotificationManager mNotificationManager; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Before + public void setUp() throws Exception { + mContext = ApplicationProvider.getApplicationContext(); + mNotificationManager = mContext.getSystemService(NotificationManager.class); + + toggleNotificationPolicyAccess(mContext, mContext.getPackageName(), true); + runAsSystemUi(() -> mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL)); + removeAutomaticZenRules(); + } + + @After + public void tearDown() { + runAsSystemUi(() -> mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL)); + removeAutomaticZenRules(); + } + + private void removeAutomaticZenRules() { + // Delete AZRs created by this test (query "as app", then delete "as system" so they are + // not preserved to be restored later). + Map<String, AutomaticZenRule> rules = mNotificationManager.getAutomaticZenRules(); + runAsSystemUi(() -> { + for (String ruleId : rules.keySet()) { + mNotificationManager.removeAutomaticZenRule(ruleId); + } + }); + } + + @Test + @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualActivation() { + AutomaticZenRule ruleToCreate = createZenRule("rule"); + String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); + Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-on", + STATE_TRUE, Condition.SOURCE_USER_ACTION); + Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off", + STATE_FALSE, Condition.SOURCE_USER_ACTION); + Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on", + STATE_TRUE); + Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off", + STATE_FALSE); + + // User manually activates -> it's active. + runAsSystemUi( + () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // User manually deactivates -> it's inactive. + runAsSystemUi( + () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // And app can activate and deactivate. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + } + + @Test + @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualDeactivation() { + AutomaticZenRule ruleToCreate = createZenRule("rule"); + String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); + Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-on", + STATE_TRUE, Condition.SOURCE_USER_ACTION); + Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off", + STATE_FALSE, Condition.SOURCE_USER_ACTION); + Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on", + STATE_TRUE); + Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off", + STATE_FALSE); + + // App activates rule. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // User manually deactivates -> it's inactive. + runAsSystemUi( + () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // User manually reactivates -> it's active. + runAsSystemUi( + () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // That manual activation removed the override-deactivate, but didn't put an + // override-activate, so app can deactivate when its natural schedule ends. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + } + + @Test + @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + public void setAutomaticZenRuleState_respectsManuallyActivated() { + AutomaticZenRule ruleToCreate = createZenRule("rule"); + String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); + Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-on", + STATE_TRUE, Condition.SOURCE_USER_ACTION); + Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on", + STATE_TRUE); + Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off", + STATE_FALSE); + + // App thinks rule should be inactive. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // Manually activate -> it's active. + runAsSystemUi(() -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // App says it should be inactive, but it's ignored. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // App says it should be active. No change now... + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // ... but when the app wants to deactivate next time, it works. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + } + + @Test + @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + public void setAutomaticZenRuleState_respectsManuallyDeactivated() { + AutomaticZenRule ruleToCreate = createZenRule("rule"); + String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); + Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off", + STATE_FALSE, Condition.SOURCE_USER_ACTION); + Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on", + STATE_TRUE); + Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off", + STATE_FALSE); + + // App activates rule. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // User manually deactivates -> it's inactive. + runAsSystemUi( + () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // App says it should be active, but it's ignored. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // App says it should be inactive. No change now... + mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // ... but when the app wants to activate next time, it works. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + } + + @Test + @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualActivationFromApp() { + AutomaticZenRule ruleToCreate = createZenRule("rule"); + String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); + Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-off", + STATE_TRUE, Condition.SOURCE_USER_ACTION); + Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off", + STATE_FALSE, Condition.SOURCE_USER_ACTION); + Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on", + STATE_TRUE); + Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off", + STATE_FALSE); + + // App activates rule. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // User manually deactivates from SysUI -> it's inactive. + runAsSystemUi( + () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // User manually activates from App -> it's active. + mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // And app can automatically deactivate it later. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + } + + @Test + @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualDeactivationFromApp() { + AutomaticZenRule ruleToCreate = createZenRule("rule"); + String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); + Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-off", + STATE_TRUE, Condition.SOURCE_USER_ACTION); + Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off", + STATE_FALSE, Condition.SOURCE_USER_ACTION); + Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on", + STATE_TRUE); + + // User manually activates from SysUI -> it's active. + runAsSystemUi( + () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate)); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + + // User manually deactivates from App -> it's inactive. + mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + + // And app can automatically activate it later. + mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate); + assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + } + + private AutomaticZenRule createZenRule(String name) { + return createZenRule(name, NotificationManager.INTERRUPTION_FILTER_PRIORITY); + } + + private AutomaticZenRule createZenRule(String name, int filter) { + return new AutomaticZenRule(name, null, + new ComponentName(mContext, ExampleActivity.class), + new Uri.Builder().scheme("scheme") + .appendPath("path") + .appendQueryParameter("fake_rule", "fake_value") + .build(), null, filter, true); + } +} diff --git a/services/tests/uiservicestests/src/android/app/NotificationSystemUtil.java b/services/tests/uiservicestests/src/android/app/NotificationSystemUtil.java new file mode 100644 index 000000000000..cf6e39b962ab --- /dev/null +++ b/services/tests/uiservicestests/src/android/app/NotificationSystemUtil.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static org.junit.Assert.assertEquals; + +import android.Manifest; +import android.content.Context; +import android.os.ParcelFileDescriptor; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.compatibility.common.util.AmUtils; +import com.android.compatibility.common.util.FileUtils; +import com.android.compatibility.common.util.SystemUtil; +import com.android.compatibility.common.util.ThrowingRunnable; + +import java.io.FileInputStream; +import java.io.IOException; + +public class NotificationSystemUtil { + + /** + * Runs a {@link ThrowingRunnable} as the Shell, while adopting SystemUI's permission (as + * checked by {@code NotificationManagerService#isCallerSystemOrSystemUi}). + */ + protected static void runAsSystemUi(@NonNull ThrowingRunnable runnable) { + SystemUtil.runWithShellPermissionIdentity( + InstrumentationRegistry.getInstrumentation().getUiAutomation(), + runnable, Manifest.permission.STATUS_BAR_SERVICE); + } + + static void toggleNotificationPolicyAccess(Context context, String packageName, + boolean on) throws IOException { + + String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName + + " " + context.getUserId(); + + runCommand(command, InstrumentationRegistry.getInstrumentation()); + AmUtils.waitForBroadcastBarrier(); + + NotificationManager nm = context.getSystemService(NotificationManager.class); + assertEquals("Notification Policy Access Grant is " + + nm.isNotificationPolicyAccessGranted() + " not " + on + " for " + + packageName, on, nm.isNotificationPolicyAccessGranted()); + } + + private static void runCommand(String command, Instrumentation instrumentation) + throws IOException { + UiAutomation uiAutomation = instrumentation.getUiAutomation(); + try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream( + uiAutomation.executeShellCommand(command))) { + FileUtils.readInputStreamFully(fis); + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 704b580a80b0..832ca51ae580 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -260,7 +260,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { return FlagsParameterization.allCombinationsOf( android.app.Flags.FLAG_API_RICH_ONGOING, FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_CLASSIFICATION_UI, - FLAG_MODES_UI); + FLAG_MODES_UI, android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS); } public PreferencesHelperTest(FlagsParameterization flags) { @@ -3381,13 +3381,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { // user 0 records remain for (int i = 0; i < user0Uids.length; i++) { assertEquals(1, - mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false, true) - .getList().size()); + mHelper.getRemovedPkgNotificationChannels(PKG_N_MR1, user0Uids[i]).size()); } // user 1 records are gone for (int i = 0; i < user1Uids.length; i++) { - assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false, true) - .getList().size()); + assertEquals(0, + mHelper.getRemovedPkgNotificationChannels(PKG_N_MR1, user1Uids[i]).size()); } } @@ -3402,8 +3401,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{UID_N_MR1})); - assertEquals(0, mHelper.getNotificationChannels( - PKG_N_MR1, UID_N_MR1, true, true).getList().size()); + assertEquals(0, mHelper.getRemovedPkgNotificationChannels(PKG_N_MR1, UID_N_MR1).size()); // Not deleted mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false, @@ -3472,7 +3470,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(mHelper.canShowBadge(PKG_O, UID_O)); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); - assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size()); + assertEquals(0, mHelper.getRemovedPkgNotificationChannels(PKG_O, UID_O).size()); assertEquals(0, mHelper.getNotificationChannelGroups(PKG_O, UID_O).size()); NotificationChannel channel = getChannel(); @@ -6836,38 +6834,11 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS) - public void testGetNotificationChannels_createIfNeeded() { - // Test setup hasn't created any channels or read package preferences yet. - // If we ask for notification channels _without_ creating, we should get no result. - ParceledListSlice<NotificationChannel> channels = mHelper.getNotificationChannels(PKG_N_MR1, - UID_N_MR1, false, false, /* createPrefsIfNeeded= */ false); - assertThat(channels.getList().size()).isEqualTo(0); - - // If we ask it to create package preferences, we expect the default channel to be created - // for N_MR1. - channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, - false, /* createPrefsIfNeeded= */ true); - assertThat(channels.getList().size()).isEqualTo(1); - assertThat(channels.getList().getFirst().getId()).isEqualTo( - NotificationChannel.DEFAULT_CHANNEL_ID); - } - - @Test @DisableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS) public void testGetNotificationChannels_neverCreatesWhenFlagOff() { - ParceledListSlice<NotificationChannel> channels; - try { - channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, - false, /* createPrefsIfNeeded= */ true); - } catch (Exception e) { - // Slog.wtf kicks in, presumably - } finally { - channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, - false, /* createPrefsIfNeeded= */ false); - assertThat(channels.getList().size()).isEqualTo(0); - } - + ParceledListSlice<NotificationChannel> channels = mHelper.getNotificationChannels(PKG_N_MR1, + UID_N_MR1, false, false); + assertThat(channels.getList().size()).isEqualTo(0); } // Test version of PreferencesHelper whose only functional difference is that it does not diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java index 51706d72cb35..902a58379ae0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java @@ -589,8 +589,7 @@ public class BackgroundActivityStartControllerTests { + "realCallerApp: null; " + "balAllowedByPiSender: BSP.ALLOW_BAL; " + "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " - + "balRequireOptInByPendingIntentCreator: true; " - + "balDontBringExistingBackgroundTaskStackToFg: true]"); + + "balRequireOptInByPendingIntentCreator: true]"); } @Test @@ -692,7 +691,6 @@ public class BackgroundActivityStartControllerTests { + "realCallerApp: null; " + "balAllowedByPiSender: BSP.ALLOW_FGS; " + "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " - + "balRequireOptInByPendingIntentCreator: true; " - + "balDontBringExistingBackgroundTaskStackToFg: true]"); + + "balRequireOptInByPendingIntentCreator: true]"); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 5ac3e483231c..7af4ede05363 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4461,7 +4461,46 @@ public class SizeCompatTests extends WindowTestsBase { // are aligned to the top of the parentAppBounds assertEquals(new Rect(0, notchHeight, 1000, 1200), appBounds); assertEquals(new Rect(0, 0, 1000, 1200), bounds); + } + + @Test + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) + public void testInFreeform_boundsSandboxedToAppBounds() { + final int dw = 2800; + final int dh = 1400; + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh) + .setNotch(notchHeight) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + Rect appBounds = new Rect(0, 0, 1000, 500); + Rect bounds = new Rect(0, 0, 1000, 600); + mTask.getWindowConfiguration().setAppBounds(appBounds); + mTask.getWindowConfiguration().setBounds(bounds); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + + // Bounds are sandboxed to appBounds in freeform. + assertDownScaled(); + assertEquals(mActivity.getWindowConfiguration().getAppBounds(), + mActivity.getWindowConfiguration().getBounds()); + + // Exit freeform. + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mTask.getWindowConfiguration().setBounds(new Rect(0, 0, dw, dh)); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + assertFitted(); + appBounds = mActivity.getWindowConfiguration().getAppBounds(); + bounds = mActivity.getWindowConfiguration().getBounds(); + // Bounds are not sandboxed to appBounds. + assertNotEquals(appBounds, bounds); + assertEquals(notchHeight, appBounds.top - bounds.top); } @Test diff --git a/telephony/java/com/android/internal/telephony/util/WorkerThread.java b/telephony/java/com/android/internal/telephony/util/WorkerThread.java new file mode 100644 index 000000000000..f5b653656352 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/util/WorkerThread.java @@ -0,0 +1,130 @@ +/* + * 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.telephony.util; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; + +/** + * Shared singleton worker thread for each process. + * + * This thread should be used for work that needs to be executed at standard priority + * but not on the main thread. This is suitable for handling asynchronous tasks that + * are ephemeral or require enough work that they shouldn't block the main thread, but + * should not block each other for more than around 100ms. + */ +public final class WorkerThread extends HandlerThread { + private static volatile WorkerThread sInstance; + private static volatile Handler sHandler; + private static volatile HandlerExecutor sHandlerExecutor; + private static final Object sLock = new Object(); + + private CountDownLatch mInitLock = new CountDownLatch(1); + + + private WorkerThread() { + super("android.telephony.worker"); + } + + private static void ensureThread() { + if (sInstance != null) return; + synchronized (sLock) { + if (sInstance != null) return; + + final WorkerThread tmpThread = new WorkerThread(); + tmpThread.start(); + + try { + tmpThread.mInitLock.await(); + } catch (InterruptedException ignored) { + } + + + sHandler = new Handler( + tmpThread.getLooper(), + /* callback= */ null, + /* async= */ false, + /* shared= */ true); + sHandlerExecutor = new HandlerExecutor(sHandler); + sInstance = tmpThread; // Note: order matters here. sInstance must be assigned last. + + } + } + + @Override + protected void onLooperPrepared() { + mInitLock.countDown(); + } + + /** + * Get the worker thread directly. + * + * Users of this thread should take care not to block it for extended periods of + * time. + * + * @return a HandlerThread, never null + */ + @NonNull public static HandlerThread get() { + ensureThread(); + return sInstance; + } + + /** + * Get a Handler that can process Runnables. + * + * @return a Handler, never null + */ + @NonNull public static Handler getHandler() { + ensureThread(); + return sHandler; + } + + /** + * Get an Executor that can process Runnables + * + * @return an Executor, never null + */ + @NonNull public static Executor getExecutor() { + ensureThread(); + return sHandlerExecutor; + } + + /** + * A method to reset the WorkerThread from scratch. + * + * This method should only be used for unit testing. In production it would have + * catastrophic consequences. Do not ever use this outside of tests. + */ + @VisibleForTesting + public static void reset() { + synchronized (sLock) { + if (sInstance == null) return; + sInstance.quitSafely(); + sInstance = null; + sHandler = null; + sHandlerExecutor = null; + ensureThread(); + } + } +} diff --git a/tests/testables/src/android/testing/OWNERS b/tests/testables/src/android/testing/OWNERS new file mode 100644 index 000000000000..f31666b43654 --- /dev/null +++ b/tests/testables/src/android/testing/OWNERS @@ -0,0 +1,2 @@ +# MessageQueue-related classes +per-file TestableLooper.java = mfasheh@google.com, shayba@google.com diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index be5c84c0353c..7d07d42b8042 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -16,11 +16,13 @@ package android.testing; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Instrumentation; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; +import android.os.SystemClock; import android.os.TestLooperManager; import android.util.ArrayMap; @@ -32,7 +34,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.reflect.Field; +import java.util.LinkedList; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -42,6 +44,12 @@ import java.util.concurrent.atomic.AtomicBoolean; * and provide an easy annotation for use with tests. * * @see TestableLooperTest TestableLooperTest for examples. + * + * @deprecated Use {@link android.os.TestLooperManager} or {@link + * org.robolectric.shadows.ShadowLooper} instead. + * This class is not actively maintained. + * Both of the recommended alternatives allow fine control of execution. + * The Robolectric class also allows advancing time. */ public class TestableLooper { @@ -50,9 +58,6 @@ public class TestableLooper { * catch crashes. */ public static final boolean HOLD_MAIN_THREAD = false; - private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; - private static final Field MESSAGE_NEXT_FIELD; - private static final Field MESSAGE_WHEN_FIELD; private Looper mLooper; private MessageQueue mQueue; @@ -61,19 +66,6 @@ public class TestableLooper { private Handler mHandler; private TestLooperManager mQueueWrapper; - static { - try { - MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); - MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); - MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); - MESSAGE_NEXT_FIELD.setAccessible(true); - MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); - MESSAGE_WHEN_FIELD.setAccessible(true); - } catch (NoSuchFieldException e) { - throw new RuntimeException("Failed to initialize TestableLooper", e); - } - } - public TestableLooper(Looper l) throws Exception { this(acquireLooperManager(l), l); } @@ -216,29 +208,17 @@ public class TestableLooper { } public void moveTimeForward(long milliSeconds) { - try { - Message msg = getMessageLinkedList(); - while (msg != null) { - long updatedWhen = msg.getWhen() - milliSeconds; - if (updatedWhen < 0) { - updatedWhen = 0; - } - MESSAGE_WHEN_FIELD.set(msg, updatedWhen); - msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + long futureWhen = SystemClock.uptimeMillis() + milliSeconds; + // Find messages in the queue enqueued within the future time, and execute them now. + while (true) { + Long peekWhen = mQueueWrapper.peekWhen(); + if (peekWhen == null || peekWhen > futureWhen) { + break; + } + Message message = mQueueWrapper.poll(); + if (message != null) { + mQueueWrapper.execute(message); } - } catch (IllegalAccessException e) { - throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e); - } - } - - private Message getMessageLinkedList() { - try { - MessageQueue queue = mLooper.getQueue(); - return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); - } catch (IllegalAccessException e) { - throw new RuntimeException( - "Access failed in TestableLooper: get - MessageQueue.mMessages", - e); } } diff --git a/tests/utils/testutils/java/android/os/test/OWNERS b/tests/utils/testutils/java/android/os/test/OWNERS index 3a9129e1bb69..6448261102fa 100644 --- a/tests/utils/testutils/java/android/os/test/OWNERS +++ b/tests/utils/testutils/java/android/os/test/OWNERS @@ -1 +1,4 @@ per-file FakePermissionEnforcer.java = file:/tests/EnforcePermission/OWNERS + +# MessageQueue-related classes +per-file TestLooper.java = mfasheh@google.com, shayba@google.com diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index 56b0a25ed2dd..bca95917b9af 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -24,31 +24,38 @@ import android.os.Looper; import android.os.Message; import android.os.MessageQueue; import android.os.SystemClock; +import android.os.TestLooperManager; import android.util.Log; +import androidx.test.InstrumentationRegistry; + import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.Queue; import java.util.concurrent.Executor; /** - * Creates a looper whose message queue can be manipulated - * This allows testing code that uses a looper to dispatch messages in a deterministic manner - * Creating a TestLooper will also install it as the looper for the current thread + * Creates a looper whose message queue can be manipulated This allows testing code that uses a + * looper to dispatch messages in a deterministic manner Creating a TestLooper will also install it + * as the looper for the current thread + * + * @deprecated Use {@link android.os.TestLooperManager} or {@link + * org.robolectric.shadows.ShadowLooper} instead. + * This class is not actively maintained. + * Both of the recommended alternatives allow fine control of execution. + * The Robolectric class also allows advancing time. */ public class TestLooper { - protected final Looper mLooper; + private final Looper mLooper; + private final TestLooperManager mTestLooperManager; + private final Clock mClock; private static final Constructor<Looper> LOOPER_CONSTRUCTOR; private static final Field THREAD_LOCAL_LOOPER_FIELD; - private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; - private static final Field MESSAGE_NEXT_FIELD; - private static final Field MESSAGE_WHEN_FIELD; - private static final Method MESSAGE_MARK_IN_USE_METHOD; private static final String TAG = "TestLooper"; - private final Clock mClock; private AutoDispatchThread mAutoDispatchThread; @@ -58,14 +65,6 @@ public class TestLooper { LOOPER_CONSTRUCTOR.setAccessible(true); THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); - MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); - MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); - MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); - MESSAGE_NEXT_FIELD.setAccessible(true); - MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); - MESSAGE_WHEN_FIELD.setAccessible(true); - MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); - MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); } catch (NoSuchFieldException | NoSuchMethodException e) { throw new RuntimeException("Failed to initialize TestLooper", e); } @@ -100,6 +99,8 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); } + mTestLooperManager = + InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); mClock = clock; } @@ -111,78 +112,61 @@ public class TestLooper { return new HandlerExecutor(new Handler(getLooper())); } - private Message getMessageLinkedList() { - try { - MessageQueue queue = mLooper.getQueue(); - return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); - } catch (IllegalAccessException e) { - throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages", - e); - } - } - public void moveTimeForward(long milliSeconds) { - try { - Message msg = getMessageLinkedList(); - while (msg != null) { - long updatedWhen = msg.getWhen() - milliSeconds; - if (updatedWhen < 0) { - updatedWhen = 0; - } - MESSAGE_WHEN_FIELD.set(msg, updatedWhen); - msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + // Drain all Messages from the queue. + Queue<Message> messages = new ArrayDeque<>(); + while (true) { + Message message = mTestLooperManager.poll(); + if (message == null) { + break; } - } catch (IllegalAccessException e) { - throw new RuntimeException("Access failed in TestLooper: set - Message.when", e); - } - } - private long currentTime() { - return mClock.uptimeMillis(); - } + // Adjust the Message's delivery time. + long newWhen = message.when - milliSeconds; + if (newWhen < 0) { + newWhen = 0; + } + message.when = newWhen; + messages.add(message); + } - private Message messageQueueNext() { - try { - long now = currentTime(); - - Message prevMsg = null; - Message msg = getMessageLinkedList(); - if (msg != null && msg.getTarget() == null) { - // Stalled by a barrier. Find the next asynchronous message in - // the queue. - do { - prevMsg = msg; - msg = (Message) MESSAGE_NEXT_FIELD.get(msg); - } while (msg != null && !msg.isAsynchronous()); + // Repost all Messages back to the queuewith a new time. + while (true) { + Message message = messages.poll(); + if (message == null) { + break; } - if (msg != null) { - if (now >= msg.getWhen()) { - // Got a message. - if (prevMsg != null) { - MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg)); - } else { - MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(), - MESSAGE_NEXT_FIELD.get(msg)); - } - MESSAGE_NEXT_FIELD.set(msg, null); - MESSAGE_MARK_IN_USE_METHOD.invoke(msg); - return msg; - } + + Runnable callback = message.getCallback(); + Handler handler = message.getTarget(); + long when = message.getWhen(); + + // The Message cannot be re-enqueued because it is marked in use. + // Make a copy of the Message and recycle the original. + // This resets {@link Message#isInUse()} but retains all other content. + { + Message newMessage = Message.obtain(); + newMessage.copyFrom(message); + newMessage.setCallback(callback); + mTestLooperManager.recycle(message); + message = newMessage; } - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Access failed in TestLooper", e); + + // Send the Message back to its Handler to be re-enqueued. + handler.sendMessageAtTime(message, when); } + } - return null; + private long currentTime() { + return mClock.uptimeMillis(); } /** * @return true if there are pending messages in the message queue */ public synchronized boolean isIdle() { - Message messageList = getMessageLinkedList(); - - return messageList != null && currentTime() >= messageList.getWhen(); + Long when = mTestLooperManager.peekWhen(); + return when != null && currentTime() >= when; } /** @@ -190,7 +174,7 @@ public class TestLooper { */ public synchronized Message nextMessage() { if (isIdle()) { - return messageQueueNext(); + return mTestLooperManager.poll(); } else { return null; } @@ -202,7 +186,7 @@ public class TestLooper { */ public synchronized void dispatchNext() { assertTrue(isIdle()); - Message msg = messageQueueNext(); + Message msg = mTestLooperManager.poll(); if (msg == null) { return; } |