diff options
131 files changed, 2564 insertions, 1314 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 78ac7749fc89..0ab477293bae 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5006,7 +5006,7 @@ package android.hardware.devicestate { } public static interface DeviceStateManager.DeviceStateCallback { - method public default void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState); + method public void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState); method public default void onSupportedStatesChanged(@NonNull java.util.List<android.hardware.devicestate.DeviceState>); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index bc45a76d861b..c1af719e91c9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1625,25 +1625,37 @@ package android.hardware.camera2.params { package android.hardware.devicestate { @FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceState { + ctor public DeviceState(@NonNull android.hardware.devicestate.DeviceState.Configuration); field public static final int PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST = 8; // 0x8 } + public static final class DeviceState.Configuration implements android.os.Parcelable { + method public int describeContents(); + method public int getIdentifier(); + method @NonNull public String getName(); + method @NonNull public java.util.Set<java.lang.Integer> getPhysicalProperties(); + method @NonNull public java.util.Set<java.lang.Integer> getSystemProperties(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.devicestate.DeviceState.Configuration> CREATOR; + } + + public static final class DeviceState.Configuration.Builder { + ctor public DeviceState.Configuration.Builder(int, @NonNull String); + method @NonNull public android.hardware.devicestate.DeviceState.Configuration build(); + method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setPhysicalProperties(@NonNull java.util.Set<java.lang.Integer>); + method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setSystemProperties(@NonNull java.util.Set<java.lang.Integer>); + } + @FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceStateManager { method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride(); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest(); - method @Deprecated @NonNull public int[] getSupportedStates(); method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); + field public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1; // 0xffffffff field public static final int MAXIMUM_DEVICE_STATE_IDENTIFIER = 10000; // 0x2710 field public static final int MINIMUM_DEVICE_STATE_IDENTIFIER = 0; // 0x0 } - public static interface DeviceStateManager.DeviceStateCallback { - method @Deprecated public default void onBaseStateChanged(int); - method @Deprecated public void onStateChanged(int); - method @Deprecated public default void onSupportedStatesChanged(@NonNull int[]); - } - public final class DeviceStateRequest { method public int getFlags(); method public int getState(); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 9f2e4739e8e6..7c803ebde384 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -776,7 +776,8 @@ public final class NotificationChannel implements Parcelable { /** * Whether or not notifications posted to this channel can bypass the Do Not Disturb - * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode. + * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode when the active policy allows + * priority channels to bypass notification filtering. */ public boolean canBypassDnd() { return mBypassDnd; diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index d2e4a614202f..b43a900bec80 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -47,6 +47,7 @@ import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.utils.CameraIdAndSessionConfiguration; import android.hardware.camera2.utils.ConcurrentCameraIdCombination; import android.hardware.camera2.utils.ExceptionUtils; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.os.Binder; @@ -204,14 +205,12 @@ public final class CameraManager { mDeviceStateListeners.add(new WeakReference<>(listener)); } + @SuppressWarnings("FlaggedApi") @Override - public final void onBaseStateChanged(int state) { - handleStateChange(state); - } - - @Override - public final void onStateChanged(int state) { - handleStateChange(state); + public void onDeviceStateChanged(DeviceState state) { + // Suppressing the FlaggedAPI warning as this specific API isn't new, just moved to + // system API which requires it to be flagged. + handleStateChange(state.getIdentifier()); } } diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 76888f338615..b214da227a2d 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -52,78 +52,6 @@ import java.util.Set; @FlaggedApi(android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_API) public final class DeviceState { /** - * Flag that indicates override requests should be cancelled when this device state becomes the - * base device state. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS} - */ - @Deprecated - public static final int FLAG_CANCEL_OVERRIDE_REQUESTS = 1 << 0; - - /** - * Flag that indicates this device state is inaccessible for applications to be placed in. This - * could be a device-state where the {@link Display#DEFAULT_DISPLAY} is not enabled. - * @hide - * @deprecated use {@link #PROPERTY_APP_INACCESSIBLE} - */ - @Deprecated - public static final int FLAG_APP_INACCESSIBLE = 1 << 1; - - /** - * Some device states can be both entered through a physical configuration as well as emulation - * through {@link DeviceStateManager#requestState}, while some states can only be entered - * through emulation and have no physical configuration to match. - * - * This flag indicates that the corresponding state can only be entered through emulation. - * @hide - * @deprecated use {@link #PROPERTY_EMULATED_ONLY} - */ - @Deprecated - public static final int FLAG_EMULATED_ONLY = 1 << 2; - - /** - * This flag indicates that the corresponding state should be automatically canceled when the - * requesting app is no longer on top. The app is considered not on top when (1) the top - * activity in the system is from a different app, (2) the device is in sleep mode, or - * (3) the keyguard shows up. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP} - */ - @Deprecated - public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3; - - /** - * This flag indicates that the corresponding state should be disabled when the device is - * overheating and reaching the critical status. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL} - */ - @Deprecated - public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4; - - /** - * This flag indicates that the corresponding state should be disabled when power save mode - * is enabled. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE} - */ - @Deprecated - public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5; - - /** @hide */ - @IntDef(prefix = {"FLAG_"}, flag = true, value = { - FLAG_CANCEL_OVERRIDE_REQUESTS, - FLAG_APP_INACCESSIBLE, - FLAG_EMULATED_ONLY, - FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP, - FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL, - FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE - }) - @Deprecated - @Retention(RetentionPolicy.SOURCE) - public @interface DeviceStateFlags {} - - /** * Property that indicates that a fold-in style foldable device is currently in a fully closed * configuration. */ @@ -302,42 +230,11 @@ public final class DeviceState { @NonNull private final DeviceState.Configuration mDeviceStateConfiguration; - @DeviceStateFlags - private final int mFlags; - /** @hide */ + @TestApi public DeviceState(@NonNull DeviceState.Configuration deviceStateConfiguration) { Objects.requireNonNull(deviceStateConfiguration, "Device StateConfiguration is null"); mDeviceStateConfiguration = deviceStateConfiguration; - mFlags = 0; - } - - /** @hide */ - public DeviceState( - @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = - MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier, - @NonNull String name, - @NonNull Set<@DeviceStateProperties Integer> properties) { - mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, properties, - Collections.emptySet()); - mFlags = 0; - } - - /** - * @deprecated Deprecated in favor of {@link #DeviceState(int, String, Set)} - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @Deprecated - public DeviceState( - @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = - MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier, - @NonNull String name, - @DeviceStateFlags int flags) { - - mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, - Collections.emptySet(), Collections.emptySet()); - mFlags = flags; } /** Returns the unique identifier for the device state. */ @@ -352,17 +249,6 @@ public final class DeviceState { return mDeviceStateConfiguration.getName(); } - /** - * @hide - * @deprecated in favor of {@link #hasProperty(int)} method - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @Deprecated - @DeviceStateFlags - public int getFlags() { - return mFlags; - } - @Override public String toString() { return "DeviceState{" + "identifier=" + mDeviceStateConfiguration.getIdentifier() @@ -388,16 +274,6 @@ public final class DeviceState { return Objects.hash(mDeviceStateConfiguration); } - /** Checks if a specific flag is set - * @hide - * @deprecated in favor of {@link #hasProperty(int)} - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @Deprecated - public boolean hasFlag(int flagToCheckFor) { - return (mFlags & flagToCheckFor) == flagToCheckFor; - } - /** * Checks if a specific property is set on this state */ @@ -438,6 +314,7 @@ public final class DeviceState { * @see DeviceStateManager * @hide */ + @TestApi public static final class Configuration implements Parcelable { /** Unique identifier for the device state. */ @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER) @@ -563,29 +440,35 @@ public final class DeviceState { }; /** @hide */ - public static class Builder { + @TestApi + public static final class Builder { private final int mIdentifier; + @NonNull private final String mName; + @NonNull private Set<@SystemDeviceStateProperties Integer> mSystemProperties = Collections.emptySet(); + @NonNull private Set<@PhysicalDeviceStateProperties Integer> mPhysicalProperties = Collections.emptySet(); - public Builder(int identifier, String name) { + public Builder(int identifier, @NonNull String name) { mIdentifier = identifier; mName = name; } /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */ + @NonNull public Builder setSystemProperties( - Set<@SystemDeviceStateProperties Integer> systemProperties) { + @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties) { mSystemProperties = systemProperties; return this; } /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */ + @NonNull public Builder setPhysicalProperties( - Set<@PhysicalDeviceStateProperties Integer> physicalProperties) { + @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) { mPhysicalProperties = physicalProperties; return this; } @@ -594,6 +477,7 @@ public final class DeviceState { * Returns a new {@link DeviceState.Configuration} whose values match the values set on * the builder. */ + @NonNull public DeviceState.Configuration build() { return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties, mPhysicalProperties); diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index a4c383343062..febc24c1465a 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -49,6 +49,7 @@ public final class DeviceStateManager { * * @hide */ + @TestApi public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1; /** @@ -96,20 +97,6 @@ public final class DeviceStateManager { /** * Returns the list of device states that are supported and can be requested with * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. - * @deprecated use {@link #getSupportedDeviceStates()} - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - @NonNull - public int[] getSupportedStates() { - return mGlobal.getSupportedStates(); - } - - /** - * Returns the list of device states that are supported and can be requested with - * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. */ @NonNull public List<DeviceState> getSupportedDeviceStates() { @@ -239,23 +226,6 @@ public final class DeviceStateManager { * Guaranteed to be called once on registration of the callback with the initial value and * then on every subsequent change in the supported states. * - * @param supportedStates the new supported states. - * - * @see DeviceStateManager#getSupportedStates() - * @deprecated use {@link #onSupportedStatesChanged(List)} - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - default void onSupportedStatesChanged(@NonNull int[] supportedStates) {} - - /** - * Called in response to a change in the states supported by the device. - * <p> - * Guaranteed to be called once on registration of the callback with the initial value and - * then on every subsequent change in the supported states. - * * The supported device states may change due to certain states becoming unavailable * due to device configuration or device conditions such as if the device is too hot or * external monitors have been connected. @@ -267,51 +237,14 @@ public final class DeviceStateManager { default void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {} /** - * Called in response to a change in the base device state. - * <p> - * The base state is the state of the device without considering any requests made through - * calls to {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)} - * from any client process. The base state is guaranteed to match the state provided with a - * call to {@link #onStateChanged(int)} when there are no active requests from any process. - * <p> - * Guaranteed to be called once on registration of the callback with the initial value and - * then on every subsequent change in the non-override state. - * - * @param state the new base device state. - * @deprecated use {@link #onDeviceStateChanged(DeviceState)} and query for physical - * properties that are relevant to your needs. - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - default void onBaseStateChanged(int state) {} - - /** * Called in response to device state changes. * <p> * Guaranteed to be called once on registration of the callback with the initial value and * then on every subsequent change in device state. * * @param state the new device state. - * @deprecated use {@link #onDeviceStateChanged(DeviceState)} - * @hide */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - void onStateChanged(int state); - - /** - * Called in response to device state changes. - * <p> - * Guaranteed to be called once on registration of the callback with the initial value and - * then on every subsequent change in device state. - * - * @param state the new device state. - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - default void onDeviceStateChanged(@NonNull DeviceState state) {} + void onDeviceStateChanged(@NonNull DeviceState state); } /** @@ -340,9 +273,6 @@ public final class DeviceStateManager { } @Override - public final void onStateChanged(int state) {} - - @Override public final void onDeviceStateChanged(@NonNull DeviceState deviceState) { final boolean folded; if (mFeatureFlags.deviceStatePropertyApi()) { diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index d6cc00d45ec4..0c8401992913 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -90,33 +90,6 @@ public final class DeviceStateManagerGlobal { } /** - * Returns the set of supported device states. - * - * @see DeviceStateManager#getSupportedStates() - */ - // TODO(b/325124054): Remove unused methods when clients are migrated. - public int[] getSupportedStates() { - synchronized (mLock) { - final DeviceStateInfo currentInfo; - if (mLastReceivedInfo != null) { - // If we have mLastReceivedInfo a callback is registered for this instance and it - // is receiving the most recent info from the server. Use that info here. - currentInfo = mLastReceivedInfo; - } else { - // If mLastReceivedInfo is null there is no registered callback so we manually - // fetch the current info. - try { - currentInfo = mDeviceStateManager.getDeviceStateInfo(); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - return getSupportedStateIdentifiersLocked(currentInfo.supportedStates); - } - } - - /** * Returns {@link List} of supported {@link DeviceState}s. * * @see DeviceStateManager#getSupportedDeviceStates() @@ -264,14 +237,8 @@ public final class DeviceStateManagerGlobal { mCallbacks.add(wrapper); if (mLastReceivedInfo != null) { - // Copy the array to prevent the callback from modifying the internal state. - final int[] supportedStates = getSupportedStateIdentifiersLocked( - mLastReceivedInfo.supportedStates); - wrapper.notifySupportedStatesChanged(supportedStates); wrapper.notifySupportedDeviceStatesChanged( List.copyOf(mLastReceivedInfo.supportedStates)); - wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState.getIdentifier()); - wrapper.notifyStateChanged(mLastReceivedInfo.currentState.getIdentifier()); wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState); } } @@ -330,15 +297,6 @@ public final class DeviceStateManagerGlobal { return -1; } - @GuardedBy("mLock") - private int[] getSupportedStateIdentifiersLocked(List<DeviceState> states) { - int[] identifiers = new int[states.size()]; - for (int i = 0; i < states.size(); i++) { - identifiers[i] = states.get(i).getIdentifier(); - } - return identifiers; - } - @Nullable private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) { for (int i = 0; i < mRequests.size(); i++) { @@ -353,12 +311,10 @@ public final class DeviceStateManagerGlobal { private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) { ArrayList<DeviceStateCallbackWrapper> callbacks; DeviceStateInfo oldInfo; - int[] supportedStateIdentifiers; synchronized (mLock) { oldInfo = mLastReceivedInfo; mLastReceivedInfo = info; callbacks = new ArrayList<>(mCallbacks); - supportedStateIdentifiers = getSupportedStateIdentifiersLocked(info.supportedStates); } final int diff = oldInfo == null ? ~0 : info.diff(oldInfo); @@ -366,18 +322,11 @@ public final class DeviceStateManagerGlobal { for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).notifySupportedDeviceStatesChanged( List.copyOf(info.supportedStates)); - callbacks.get(i).notifySupportedStatesChanged(supportedStateIdentifiers); - } - } - if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) { - for (int i = 0; i < callbacks.size(); i++) { - callbacks.get(i).notifyBaseStateChanged(info.baseState.getIdentifier()); } } if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) { for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).notifyDeviceStateChanged(info.currentState); - callbacks.get(i).notifyStateChanged(info.currentState.getIdentifier()); } } } @@ -439,26 +388,11 @@ public final class DeviceStateManagerGlobal { mExecutor = executor; } - void notifySupportedStatesChanged(int[] newSupportedStates) { - mExecutor.execute(() -> - mDeviceStateCallback.onSupportedStatesChanged(newSupportedStates)); - } - void notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates) { mExecutor.execute(() -> mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates)); } - void notifyBaseStateChanged(int newBaseState) { - execute("notifyBaseStateChanged", - () -> mDeviceStateCallback.onBaseStateChanged(newBaseState)); - } - - void notifyStateChanged(int newDeviceState) { - execute("notifyStateChanged", - () -> mDeviceStateCallback.onStateChanged(newDeviceState)); - } - void notifyDeviceStateChanged(DeviceState newDeviceState) { execute("notifyDeviceStateChanged", () -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState)); diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java index 893d765e48da..7665e2f173b9 100644 --- a/core/java/android/hardware/devicestate/DeviceStateRequest.java +++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java @@ -115,8 +115,8 @@ public final class DeviceStateRequest { * requested state. * <p> * Guaranteed to be called after a call to - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} with a state - * matching the requested state. + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} with a + * state matching the requested state. */ default void onRequestActivated(@NonNull DeviceStateRequest request) {} @@ -124,7 +124,7 @@ public final class DeviceStateRequest { * Called to indicate the request has been temporarily suspended. * <p> * Guaranteed to be called before a call to - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}. + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}. */ default void onRequestSuspended(@NonNull DeviceStateRequest request) {} @@ -134,7 +134,7 @@ public final class DeviceStateRequest { * DeviceStateRequest.Callback)}. * <p> * Guaranteed to be called before a call to - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}. + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}. * <p> * Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to * occur before this method. diff --git a/core/java/android/hardware/devicestate/DeviceStateUtil.java b/core/java/android/hardware/devicestate/DeviceStateUtil.java new file mode 100644 index 000000000000..627e740f4a5c --- /dev/null +++ b/core/java/android/hardware/devicestate/DeviceStateUtil.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.devicestate; + +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; + +import android.annotation.NonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * Utilities for {@link DeviceStateManager}. + * @hide + */ +public class DeviceStateUtil { + private DeviceStateUtil() { } + + /** + * Returns the state identifier of the {@link DeviceState} that matches the + * {@code currentState}s physical properties. This will return the identifier of the + * {@link DeviceState} that matches the devices physical configuration. + * + * Returns {@link INVALID_DEVICE_STATE_IDENTIFIER} if there is no {@link DeviceState} in the + * provided list of {@code supportedStates} that matches. + * @hide + */ + public static int calculateBaseStateIdentifier(@NonNull DeviceState currentState, + @NonNull List<DeviceState> supportedStates) { + DeviceState.Configuration stateConfiguration = currentState.getConfiguration(); + for (int i = 0; i < supportedStates.size(); i++) { + DeviceState stateToCompare = supportedStates.get(i); + if (stateToCompare.getConfiguration().getPhysicalProperties().isEmpty()) { + continue; + } + if (isDeviceStateMatchingPhysicalProperties(stateConfiguration.getPhysicalProperties(), + supportedStates.get(i))) { + return supportedStates.get(i).getIdentifier(); + } + } + return INVALID_DEVICE_STATE_IDENTIFIER; + } + + /** + * Returns if the physical properties provided, matches the same physical properties on the + * provided {@link DeviceState}. + */ + private static boolean isDeviceStateMatchingPhysicalProperties( + Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties, + DeviceState state) { + Iterator<@DeviceState.PhysicalDeviceStateProperties Integer> iterator = + physicalProperties.iterator(); + while (iterator.hasNext()) { + if (!state.hasProperty(iterator.next())) { + return false; + } + } + return true; + } + +} diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index eb26a768f2a6..4894fb1ec452 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1653,6 +1653,22 @@ public final class DisplayManager { } /** + * Allows internal application to restrict display modes to specified modeIds + * + * @param displayId display that restrictions will be applied to + * @param modeIds allowed mode ids + * + * @hide + */ + @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES") + public void requestDisplayModes(int displayId, @Nullable int[] modeIds) { + if (modeIds != null && modeIds.length == 0) { + throw new IllegalArgumentException("requestDisplayModes: modesIds can't be empty"); + } + mGlobal.requestDisplayModes(displayId, modeIds); + } + + /** * Listens for changes in available display devices. */ public interface DisplayListener { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 75f0ceb7e651..3d7b714a2f5b 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -38,6 +38,7 @@ import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.graphics.common.DisplayDecorationSupport; import android.media.projection.IMediaProjection; import android.media.projection.MediaProjection; +import android.os.Binder; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; @@ -138,6 +139,8 @@ public final class DisplayManagerGlobal { private int mWifiDisplayScanNestCount; + private final Binder mToken = new Binder(); + @VisibleForTesting public DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; @@ -1182,6 +1185,20 @@ public final class DisplayManagerGlobal { } } + /** + * Sets allowed display mode ids + * + * @hide + */ + @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES") + public void requestDisplayModes(int displayId, @Nullable int[] modeIds) { + try { + mDm.requestDisplayModes(mToken, displayId, modeIds); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, @DisplayEvent int event) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 83de4e45cf2f..70efc6f2e33f 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -235,4 +235,8 @@ interface IDisplayManager { // Disable a connected display that is enabled. @EnforcePermission("MANAGE_DISPLAYS") void disableConnectedDisplay(int displayId); + + // Restricts display modes to specified modeIds. + @EnforcePermission("RESTRICT_DISPLAY_MODES") + void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0869af5b1fd8..7e46818da745 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8150,6 +8150,14 @@ <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES" android:protectionLevel="signature|privileged"/> + <!-- Allows internal applications to restrict display modes + <p>Not for use by third-party applications. + <p>Protection level: signature + @hide + --> + <permission android:name="android.permission.RESTRICT_DISPLAY_MODES" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 48ba52621f64..fc1fbb5368dc 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -120,6 +120,10 @@ public class ImeInsetsSourceConsumerTest { final var statsToken = ImeTracker.Token.empty(); mImeConsumer.onWindowFocusGained(true); mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); + // Called once through the show flow. + verify(mController).applyAnimation( + eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, + eq(statsToken)); // set control and verify visibility is applied. InsetsSourceControl control = new InsetsSourceControl(ID_IME, @@ -127,11 +131,11 @@ public class ImeInsetsSourceConsumerTest { mController.onControlsChanged(new InsetsSourceControl[] { control }); // IME show animation should be triggered when control becomes available. verify(mController).applyAnimation( - eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, + and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( - eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, + and(not(eq(statsToken)), notNull())); }); } @@ -159,6 +163,10 @@ public class ImeInsetsSourceConsumerTest { final var statsToken = ImeTracker.Token.empty(); if (imeVisible) { mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); + // Called once through the show flow. + verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), + eq(true) /* show */, eq(true) /* fromIme */, + eq(false) /* skipAnim */, eq(statsToken)); } // set control and verify visibility is applied. @@ -184,13 +192,17 @@ public class ImeInsetsSourceConsumerTest { if (!hasViewFocus) { final var statsTokenNext = ImeTracker.Token.empty(); mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsTokenNext); + // Called once through the show flow. + verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), + eq(true) /* show */, eq(true) /* fromIme */, + eq(false) /* skipAnim */, eq(statsTokenNext)); mController.onControlsChanged(new InsetsSourceControl[]{ control }); // Verify IME show animation should be triggered when control becomes available and // the animation will be skipped by getAndClearSkipAnimationOnce invoked. verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), - eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(statsTokenNext)); + eq(true) /* show */, eq(false) /* fromIme */, + eq(true) /* skipAnim */, and(not(eq(statsToken)), notNull())); } }); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java index fa35b632a6b3..98935e95deaf 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java @@ -19,13 +19,14 @@ package androidx.window.common; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; -import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE; import static androidx.window.common.CommonFoldingFeature.parseListFromString; import android.annotation.NonNull; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; +import android.hardware.devicestate.DeviceStateUtil; import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; @@ -54,29 +55,27 @@ public final class DeviceStateManagerFoldingFeatureProducer private static final boolean DEBUG = false; /** - * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to + * Emulated device state + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to * {@link CommonFoldingFeature.State} map. */ private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); /** - * Emulated device state received via - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}. - * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with - * physical device states. They represent the state of the device when various software - * features and APIs are applied. The emulated states generally consist of all "base" states, - * but may have additional states such as "concurrent" or "rear display". Concurrent mode for - * example is activated via public API and can be active in both the "open" and "half folded" - * device states. + * Device state received via + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}. + * The identifier returned through {@link DeviceState#getIdentifier()} may not correspond 1:1 + * with the physical state of the device. This could correspond to the system state of the + * device when various software features or overrides are applied. The emulated states generally + * consist of all "base" states, but may have additional states such as "concurrent" or + * "rear display". Concurrent mode for example is activated via public API and can be active in + * both the "open" and "half folded" device states. */ - private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; + private DeviceState mCurrentDeviceState = new DeviceState( + new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER, + "INVALID").build()); - /** - * Base device state received via - * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}. - * "Base" in this context means the "physical" state of the device. - */ - private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; + private List<DeviceState> mSupportedStates; @NonNull private final RawFoldingFeatureProducer mRawFoldSupplier; @@ -85,22 +84,11 @@ public final class DeviceStateManagerFoldingFeatureProducer private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() { @Override - public void onStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { mCurrentDeviceState = state; mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer .this::notifyFoldingFeatureChange); } - - @Override - public void onBaseStateChanged(int state) { - mCurrentBaseDeviceState = state; - - if (mDeviceStateToPostureMap.get(mCurrentDeviceState) - == COMMON_STATE_USE_BASE_STATE) { - mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer - .this::notifyFoldingFeatureChange); - } - } }; public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context, @@ -109,6 +97,7 @@ public final class DeviceStateManagerFoldingFeatureProducer mRawFoldSupplier = rawFoldSupplier; String[] deviceStatePosturePairs = context.getResources() .getStringArray(R.array.config_device_state_postures); + mSupportedStates = deviceStateManager.getSupportedDeviceStates(); boolean isHalfOpenedSupported = false; for (String deviceStatePosturePair : deviceStatePosturePairs) { String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); @@ -168,7 +157,7 @@ public final class DeviceStateManagerFoldingFeatureProducer */ private boolean isCurrentStateValid() { // If the device state is not found in the map, indexOfKey returns a negative number. - return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0; + return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState.getIdentifier()) >= 0; } @Override @@ -177,7 +166,9 @@ public final class DeviceStateManagerFoldingFeatureProducer if (hasListeners()) { mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange); } else { - mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; + mCurrentDeviceState = new DeviceState( + new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER, + "INVALID").build()); mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange); } } @@ -251,10 +242,13 @@ public final class DeviceStateManagerFoldingFeatureProducer @CommonFoldingFeature.State private int currentHingeState() { @CommonFoldingFeature.State - int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN); + int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState.getIdentifier(), + COMMON_STATE_UNKNOWN); if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) { - posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN); + posture = mDeviceStateToPostureMap.get( + DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState, + mSupportedStates), COMMON_STATE_UNKNOWN); } return posture; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index d31bf2a662a3..a3d2d7f4dcdf 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -20,6 +20,7 @@ import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STA import android.app.Activity; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateRequest; import android.hardware.display.DisplayManager; @@ -40,6 +41,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -101,7 +103,9 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, mDisplayManager = context.getSystemService(DisplayManager.class); mExecutor = context.getMainExecutor(); - mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates(); + // TODO(b/329436166): Update the usage of device state manager API's + mCurrentSupportedDeviceStates = getSupportedStateIdentifiers( + mDeviceStateManager.getSupportedDeviceStates()); mFoldedDeviceStates = context.getResources().getIntArray( R.array.config_foldedDeviceStates); @@ -446,9 +450,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } @Override - public void onSupportedStatesChanged(int[] supportedStates) { + public void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) { synchronized (mLock) { - mCurrentSupportedDeviceStates = supportedStates; + // TODO(b/329436166): Update the usage of device state manager API's + mCurrentSupportedDeviceStates = getSupportedStateIdentifiers(supportedStates); updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); updateRearDisplayPresentationStatusListeners( getCurrentRearDisplayPresentationModeStatus()); @@ -456,9 +461,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } @Override - public void onStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { synchronized (mLock) { - mCurrentDeviceState = state; + // TODO(b/329436166): Update the usage of device state manager API's + mCurrentDeviceState = state.getIdentifier(); updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); updateRearDisplayPresentationStatusListeners( getCurrentRearDisplayPresentationModeStatus()); @@ -482,6 +488,15 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, return WindowAreaComponent.STATUS_AVAILABLE; } + // TODO(b/329436166): Remove and update the usage of device state manager API's + private int[] getSupportedStateIdentifiers(@NonNull List<DeviceState> states) { + int[] identifiers = new int[states.size()]; + for (int i = 0; i < states.size(); i++) { + identifiers[i] = states.get(i).getIdentifier(); + } + return identifiers; + } + /** * Helper method to determine if a rear display session is currently active by checking * if the current device state is that which corresponds to {@code mRearDisplayState}. diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 7dd39613b438..00fb298ea1cc 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -272,6 +272,10 @@ <dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen> <!-- Corner radius for expanded view while it is being dragged --> <dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen> + <!-- Width of the box around bottom center of the screen where drag only leads to dismiss --> + <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen> + <!-- Height of the box around bottom center of the screen where drag only leads to dismiss --> + <dimen name="bubble_bar_dismiss_zone_height">242dp</dimen> <!-- Bottom and end margin for compat buttons. --> <dimen name="compat_button_margin">24dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index 056598b86d58..b5b8a81c8886 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -17,8 +17,10 @@ package com.android.wm.shell.bubbles.bar import android.annotation.SuppressLint +import android.graphics.RectF import android.view.MotionEvent import android.view.View +import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.common.bubbles.BubbleBarLocation import com.android.wm.shell.common.bubbles.DismissView @@ -43,6 +45,8 @@ class BubbleBarExpandedViewDragController( private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> = MagnetizedObject.magnetizeView(expandedView) private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget + private val dismissZoneHeight: Int + private val dismissZoneWidth: Int init { magnetizedExpandedView.magnetListener = MagnetListener() @@ -74,6 +78,11 @@ class BubbleBarExpandedViewDragController( } return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed } + + dismissZoneHeight = + dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_height) + dismissZoneWidth = + dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_width) } /** Listener to receive callback about dragging events */ @@ -97,12 +106,23 @@ class BubbleBarExpandedViewDragController( private var isMoving = false private var screenCenterX: Int = -1 private var isOnLeft = false + private val dismissZone = RectF() override fun onDown(v: View, ev: MotionEvent): Boolean { // While animating, don't allow new touch events if (expandedView.isAnimating) return false - screenCenterX = bubblePositioner.screenRect.centerX() isOnLeft = bubblePositioner.isBubbleBarOnLeft + + val screenRect = bubblePositioner.screenRect + screenCenterX = screenRect.centerX() + val screenBottom = screenRect.bottom + + dismissZone.set( + screenCenterX - dismissZoneWidth / 2f, + (screenBottom - dismissZoneHeight).toFloat(), + screenCenterX + dismissZoneHeight / 2f, + screenBottom.toFloat() + ) return true } @@ -122,6 +142,11 @@ class BubbleBarExpandedViewDragController( expandedView.translationY = expandedViewInitialTranslationY + dy dismissView.show() + // Check if we are in the zone around dismiss view where drag can only lead to dismiss + if (dismissZone.contains(ev.rawX, ev.rawY)) { + return + } + if (isOnLeft && ev.rawX > screenCenterX) { isOnLeft = false dragListener.onLocationChanged(BubbleBarLocation.RIGHT) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java index 8b4ac1a8dc79..d17e8620ff12 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java @@ -107,7 +107,7 @@ public class DevicePostureController { DeviceStateManager.class); if (deviceStateManager != null) { deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged( - mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN))); + mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN))); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 3266c12f3ad2..b151a538670d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -60,10 +60,12 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; @@ -161,6 +163,11 @@ final class SettingsState { private static final String APEX_DIR = "/apex"; private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb"; + private static final String STORAGE_MIGRATION_FLAG = + "core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1"; + private static final String STORAGE_MIGRATION_LOG = + "/metadata/aconfig/flags/storage_migration.log"; + /** * This tag is applied to all aconfig default value-loaded flags. */ @@ -1439,6 +1446,20 @@ final class SettingsState { } } + if (name != null && name.equals(STORAGE_MIGRATION_FLAG) && value.equals("true")) { + File file = new File(STORAGE_MIGRATION_LOG); + if (!file.exists()) { + try (BufferedWriter writer = + new BufferedWriter(new FileWriter(STORAGE_MIGRATION_LOG))) { + final long timestamp = System.currentTimeMillis(); + String entry = String.format("%d | Log init", timestamp); + writer.write(entry); + } catch (IOException e) { + Slog.e(LOG_TAG, "failed to write storage migration file", e); + } + } + } + mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag, fromSystem, id, isPreservedInRestore)); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index 2e14e9b8be4c..c572bdb57c6a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -33,3 +33,10 @@ flag { bug: "327383546" is_fixed_read_only: true } + +flag { + name: "storage_test_mission_1" + namespace: "core_experiments_team_internal" + description: "If this flag is detected as true on boot, writes a logfile to track storage migration correctness." + bug: "328444881" +} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 3c18f17b8e96..f12320163bc6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -434,7 +434,7 @@ </intent-filter> </receiver> - <activity android:name=".screenshot.LongScreenshotActivity" + <activity android:name=".screenshot.scroll.LongScreenshotActivity" android:theme="@style/LongScreenshotActivity" android:process=":screenshot" android:exported="false" diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 63ec54fbef9c..82083f99ba3e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -605,6 +605,8 @@ private class SwipeTransition( override val isInitiatedByUserInput = true + override var bouncingScene: SceneKey? = null + /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) @@ -694,14 +696,31 @@ private class SwipeTransition( ): OffsetAnimation { return startOffsetAnimation { val animatable = Animatable(dragOffset, OffsetVisibilityThreshold) + val isTargetGreater = targetOffset > animatable.value val job = coroutineScope .launch { - animatable.animateTo( - targetValue = targetOffset, - animationSpec = swipeSpec, - initialVelocity = initialVelocity, - ) + try { + animatable.animateTo( + targetValue = targetOffset, + animationSpec = swipeSpec, + initialVelocity = initialVelocity, + ) { + if (bouncingScene == null) { + val isBouncing = + if (isTargetGreater) { + value > targetOffset + } else { + value < targetOffset + } + if (isBouncing) { + bouncingScene = targetScene + } + } + } + } finally { + bouncingScene = null + } } // Make sure that we settle to target scene at the end of the animation or if // the animation is cancelled. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index c7186da6b961..f1177a8eb864 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -588,7 +588,8 @@ private inline fun <T> computeValue( // TODO(b/290184746): Make sure that we don't overflow transformations associated to a // range. val directionSign = if (transition.isUpOrLeft) -1 else 1 - val overscrollProgress = transition.progress.let { if (it > 1f) it - 1f else it } + val isToScene = overscroll.scene == transition.toScene + val overscrollProgress = transition.progress.let { if (isToScene) it - 1f else it } val progress = directionSign * overscrollProgress val rangeProgress = propertySpec.range?.progress(progress) ?: progress diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index e6f5d585e915..617a8ea0b6cd 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -255,6 +255,12 @@ sealed interface TransitionState { */ val overscrollScope: OverscrollScope + /** + * The scene around which the transition is currently bouncing. When not `null`, this + * transition is currently oscillating around this scene and will soon settle to that scene. + */ + val bouncingScene: SceneKey? + companion object { const val DistanceUnspecified = 0f } @@ -287,9 +293,10 @@ internal abstract class BaseSceneTransitionLayoutState( val transition = currentTransition ?: return null if (transition !is TransitionState.HasOverscrollProperties) return null val progress = transition.progress + val bouncingScene = transition.bouncingScene return when { - progress < 0f -> fromOverscrollSpec - progress > 1f -> toOverscrollSpec + progress < 0f || bouncingScene == transition.fromScene -> fromOverscrollSpec + progress > 1f || bouncingScene == transition.toScene -> toOverscrollSpec else -> null } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 26e01fefcb9b..080476101821 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -16,6 +16,8 @@ package com.android.compose.animation.scene +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.Orientation @@ -752,4 +754,55 @@ class ElementTest { assertThat(state.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f) } + + @Test + fun elementTransitionWithDistanceDuringOverscrollBouncing() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + val state = + setupOverscrollScenario( + layoutWidth = layoutWidth, + layoutHeight = layoutHeight, + sceneTransitions = { + defaultSwipeSpec = + spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow, + ) + + overscroll(TestScenes.SceneB, Orientation.Vertical) { + // On overscroll 100% -> Foo should translate by layoutHeight + translate(TestElements.Foo, y = { absoluteDistance }) + } + }, + firstScroll = 1f, // 100% scroll + ) + + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) + fooElement.assertTopPositionInRootIsEqualTo(0.dp) + + rule.onRoot().performTouchInput { + // Scroll another 50% + moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000) + } + + val transition = state.currentTransition + assertThat(transition).isNotNull() + transition as TransitionState.HasOverscrollProperties + + // Scroll 150% (100% scroll + 50% overscroll) + assertThat(transition.progress).isEqualTo(1.5f) + assertThat(state.currentOverscrollSpec).isNotNull() + fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f)) + + // finger raised + rule.onRoot().performTouchInput { up() } + + // The target value is 1f, but the spring (defaultSwipeSpec) allows you to go to a lower + // value. + rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f } + + assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(transition.bouncingScene).isEqualTo(transition.toScene) + } } diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt index 73a66c629024..a32fe2273804 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt @@ -27,6 +27,7 @@ fun transition( isInitiatedByUserInput: Boolean = false, isUserInputOngoing: Boolean = false, isUpOrLeft: Boolean = false, + bouncingScene: SceneKey? = null, orientation: Orientation = Orientation.Horizontal, ): TransitionState.Transition { return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties { @@ -37,6 +38,7 @@ fun transition( override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput override val isUserInputOngoing: Boolean = isUserInputOngoing override val isUpOrLeft: Boolean = isUpOrLeft + override val bouncingScene: SceneKey? = bouncingScene override val orientation: Orientation = orientation override val overscrollScope: OverscrollScope = object : OverscrollScope { 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 b562d7b5ee48..6780e5721af3 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml @@ -85,6 +85,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="8dp" + android:focusable="false" androidprv:layout_constraintVertical_bias="1.0" androidprv:layout_constraintDimensionRatio="1.0" androidprv:layout_constraintStart_toStartOf="parent" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml index d9011c26dfd7..d991581c2665 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml @@ -57,6 +57,7 @@ android:id="@+id/lockPatternView" android:layout_width="0dp" android:layout_height="0dp" + android:focusable="false" androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline" androidprv:layout_constraintBottom_toBottomOf="parent" androidprv:layout_constraintLeft_toLeftOf="parent" diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml index 5fe74aa6817f..2d63c8da54f9 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml @@ -26,7 +26,7 @@ android:layout_height="match_parent"> android:paddingHorizontal="16dp" android:paddingVertical="16dp" android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/bottomGuideline" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/rightGuideline" app:layout_constraintStart_toStartOf="@+id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline" /> @@ -59,7 +59,7 @@ android:layout_height="match_parent"> android:layout_width="0dp" android:layout_height="0dp" android:fillViewport="true" - android:padding="16dp" + android:padding="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" @@ -82,20 +82,20 @@ android:layout_height="match_parent"> app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="wrap_content" + <TextView + android:id="@+id/logo_description" + android:layout_width="0dp" android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingHorizontal="0dp" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" + android:ellipsize="marquee" + android:gravity="@integer/biometric_dialog_text_gravity" + android:marqueeRepeatLimit="1" + android:singleLine="true" + android:textAlignment="viewStart" + android:paddingLeft="8dp" + app:layout_constraintBottom_toBottomOf="@+id/logo" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.0" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" - app:layout_constraintVertical_bias="0.0" /> + app:layout_constraintStart_toEndOf="@+id/logo" + app:layout_constraintTop_toTopOf="@+id/logo" /> <Space android:id="@+id/space_above_content" @@ -108,6 +108,7 @@ android:layout_height="match_parent"> style="@style/TextAppearance.AuthCredential.Title" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="12dp" android:gravity="@integer/biometric_dialog_text_gravity" android:paddingHorizontal="0dp" android:textAlignment="viewStart" @@ -124,6 +125,7 @@ android:layout_height="match_parent"> style="@style/TextAppearance.AuthCredential.Subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="12dp" android:gravity="@integer/biometric_dialog_text_gravity" android:paddingHorizontal="0dp" android:textAlignment="viewStart" @@ -133,14 +135,14 @@ android:layout_height="match_parent"> app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> - <TextView - android:id="@+id/description" - style="@style/TextAppearance.AuthCredential.Description" + <LinearLayout + android:id="@+id/customized_view_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" + android:gravity="center_vertical" + android:orientation="vertical" android:paddingHorizontal="0dp" - android:textAlignment="viewStart" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" @@ -149,19 +151,20 @@ android:layout_height="match_parent"> app:layout_constraintVertical_bias="0.0" /> <TextView - android:id="@+id/logo_description" - android:layout_width="0dp" + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:ellipsize="marquee" android:gravity="@integer/biometric_dialog_text_gravity" - android:marqueeRepeatLimit="1" - android:singleLine="true" + android:paddingHorizontal="0dp" android:textAlignment="viewStart" - android:paddingLeft="8dp" - app:layout_constraintBottom_toBottomOf="@+id/logo" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/logo" - app:layout_constraintTop_toTopOf="@+id/logo" /> + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/subtitle" + app:layout_constraintVertical_bias="0.0" /> + <androidx.constraintlayout.widget.Barrier android:id="@+id/contentBarrier" @@ -178,7 +181,7 @@ android:layout_height="match_parent"> android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" android:accessibilityLiveRegion="polite" android:fadingEdge="horizontal" android:gravity="center_horizontal" @@ -192,80 +195,15 @@ android:layout_height="match_parent"> app:layout_constraintTop_toBottomOf="@+id/biometric_icon" app:layout_constraintVertical_bias="0.0" /> - <!-- Negative Button, reserved for app --> - <Button - android:id="@+id/button_negative" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:ellipsize="end" - android:maxLines="2" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintStart_toStartOf="@+id/scrollView" /> - - <!-- Cancel Button, replaces negative button when biometric is accepted --> - <Button - android:id="@+id/button_cancel" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:text="@string/cancel" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintStart_toStartOf="@+id/scrollView" /> - - <!-- "Use Credential" Button, replaces if device credential is allowed --> - <Button - android:id="@+id/button_use_credential" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintStart_toStartOf="@+id/scrollView" /> - - <!-- Positive Button --> - <Button - android:id="@+id/button_confirm" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_confirm" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintEnd_toEndOf="@+id/scrollView" - tools:visibility="invisible" /> - - <!-- Try Again Button --> - <Button - android:id="@+id/button_try_again" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_try_again" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintEnd_toEndOf="@+id/scrollView" /> + <include + android:id="@+id/button_bar" + layout="@layout/biometric_prompt_button_bar" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + app:layout_constraintEnd_toEndOf="@id/scrollView" + app:layout_constraintStart_toStartOf="@id/scrollView" + app:layout_constraintTop_toBottomOf="@id/scrollView" /> <!-- Guidelines for setting panel border --> <androidx.constraintlayout.widget.Barrier @@ -282,7 +220,7 @@ android:layout_height="match_parent"> android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" /> + app:constraint_referenced_ids="button_bar" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/leftGuideline" diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml index 5b30dfb77342..329fc466d378 100644 --- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml @@ -23,92 +23,29 @@ android:clickable="true" android:clipToOutline="true" android:importantForAccessibility="no" - android:paddingHorizontal="16dp" - android:paddingVertical="16dp" android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/bottomGuideline" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/rightGuideline" app:layout_constraintStart_toStartOf="@+id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topBarrier" /> - <Button - android:id="@+id/button_negative" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:ellipsize="end" - android:maxLines="2" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <Button - android:id="@+id/button_cancel" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:text="@string/cancel" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <Button - android:id="@+id/button_use_credential" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <Button - android:id="@+id/button_confirm" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_confirm" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" - tools:visibility="invisible" /> - - <Button - android:id="@+id/button_try_again" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_try_again" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" /> - - <!-- Negative Button, reserved for app --> + <include + layout="@layout/biometric_prompt_button_bar" + android:id="@+id/button_bar" + android:layout_width="0dp" + android:layout_height="match_parent" + app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + app:layout_constraintEnd_toEndOf="@id/panel" + app:layout_constraintStart_toStartOf="@id/panel"/> <ScrollView android:id="@+id/scrollView" android:layout_width="0dp" android:layout_height="wrap_content" android:fillViewport="true" - android:padding="16dp" + android:paddingBottom="36dp" + android:paddingHorizontal="24dp" + android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/biometric_icon" @@ -134,18 +71,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="wrap_content" + <TextView + android:id="@+id/logo_description" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" + android:ellipsize="marquee" + android:gravity="@integer/biometric_dialog_text_gravity" + android:marqueeRepeatLimit="1" + android:singleLine="true" + android:paddingTop="16dp" + app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> + app:layout_constraintTop_toBottomOf="@+id/logo" /> <Space android:id="@+id/space_above_content" @@ -159,6 +97,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/subtitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -170,23 +109,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/contentBarrier" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> - <TextView - android:id="@+id/logo_description" - android:layout_width="match_parent" + <LinearLayout + android:id="@+id/customized_view_container" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:ellipsize="marquee" - android:gravity="@integer/biometric_dialog_text_gravity" - android:marqueeRepeatLimit="1" - android:singleLine="true" - app:layout_constraintBottom_toTopOf="@+id/title" + android:gravity="center_vertical" + android:orientation="vertical" + android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo" /> + app:layout_constraintTop_toBottomOf="@+id/subtitle" /> <TextView android:id="@+id/description" @@ -215,7 +155,7 @@ android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" android:accessibilityLiveRegion="polite" android:fadingEdge="horizontal" android:gravity="center_horizontal" @@ -247,7 +187,7 @@ android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" /> + app:constraint_referenced_ids="button_bar" /> <!-- Guidelines for setting panel border --> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml index cb638ee87834..bcc7bca8c915 100644 --- a/packages/SystemUI/res/layout/app_clips_screenshot.xml +++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml @@ -69,7 +69,7 @@ tools:minHeight="100dp" tools:minWidth="100dp" /> - <com.android.systemui.screenshot.CropView + <com.android.systemui.screenshot.scroll.CropView android:id="@+id/crop_view" android:layout_width="0px" android:layout_height="0px" diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml new file mode 100644 index 000000000000..810c7433e4ad --- /dev/null +++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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. + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <!-- Negative Button, reserved for app --> + <Button + android:id="@+id/button_negative" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="24dp" + android:ellipsize="end" + android:maxLines="2" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <!-- Cancel Button, replaces negative button when biometric is accepted --> + <Button + android:id="@+id/button_cancel" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="24dp" + android:text="@string/cancel" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <!-- "Use Credential" Button, replaces if device credential is allowed --> + <Button + android:id="@+id/button_use_credential" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="24dp" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <!-- Positive Button --> + <Button + android:id="@+id/button_confirm" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginRight="24dp" + android:ellipsize="end" + android:maxLines="2" + android:text="@string/biometric_dialog_confirm" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + <!-- Try Again Button --> + <Button + android:id="@+id/button_try_again" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginRight="24dp" + android:ellipsize="end" + android:maxLines="2" + android:text="@string/biometric_dialog_try_again" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml index 74292b4edf0a..6391813754d0 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml @@ -23,43 +23,29 @@ android:clickable="true" android:clipToOutline="true" android:importantForAccessibility="no" - android:paddingHorizontal="16dp" - android:paddingVertical="16dp" android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/bottomGuideline" - app:layout_constraintEnd_toStartOf="@+id/rightGuideline" - app:layout_constraintStart_toStartOf="@+id/leftGuideline" - app:layout_constraintTop_toTopOf="@+id/topBarrier" /> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.8" - tools:srcCompat="@tools:sample/avatars" /> + app:layout_constraintEnd_toEndOf="@id/rightGuideline" + app:layout_constraintStart_toStartOf="@id/leftGuideline" + app:layout_constraintTop_toTopOf="@id/topBarrier" /> - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon_overlay" + <include + android:id="@+id/button_bar" + layout="@layout/biometric_prompt_button_bar" android:layout_width="0dp" - android:layout_height="0dp" - android:layout_gravity="center" - android:contentDescription="@null" - android:scaleType="fitXY" - app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" - app:layout_constraintEnd_toEndOf="@+id/biometric_icon" - app:layout_constraintStart_toStartOf="@+id/biometric_icon" - app:layout_constraintTop_toTopOf="@+id/biometric_icon" /> + android:layout_height="match_parent" + app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + app:layout_constraintEnd_toEndOf="@id/panel" + app:layout_constraintStart_toStartOf="@id/panel" /> <ScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="wrap_content" android:fillViewport="true" - android:padding="16dp" + android:paddingBottom="36dp" + android:paddingHorizontal="24dp" + android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/biometric_icon" @@ -79,24 +65,22 @@ android:layout_height="@dimen/biometric_auth_icon_size" android:layout_gravity="center" android:scaleType="fitXY" - android:visibility="visible" app:layout_constraintBottom_toTopOf="@+id/logo_description" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="wrap_content" + <TextView + android:id="@+id/logo_description" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" + android:gravity="@integer/biometric_dialog_text_gravity" + android:singleLine="true" + app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> + app:layout_constraintTop_toBottomOf="@+id/logo" /> <Space android:id="@+id/space_above_content" @@ -110,6 +94,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/subtitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -121,23 +106,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/contentBarrier" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> - <TextView - android:id="@+id/logo_description" + <LinearLayout + android:id="@+id/customized_view_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:ellipsize="marquee" - android:gravity="@integer/biometric_dialog_text_gravity" - android:marqueeRepeatLimit="1" - android:singleLine="true" - app:layout_constraintBottom_toTopOf="@+id/title" + android:gravity="center_vertical" + android:orientation="vertical" + android:visibility="gone" + android:paddingTop="8dp" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo" /> + app:layout_constraintTop_toBottomOf="@+id/subtitle" /> <TextView android:id="@+id/description" @@ -145,6 +131,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -165,96 +152,17 @@ android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" android:accessibilityLiveRegion="polite" android:fadingEdge="horizontal" android:gravity="center_horizontal" - android:marqueeRepeatLimit="marquee_forever" android:scrollHorizontally="true" - android:textColor="@color/biometric_dialog_gray" - android:textSize="12sp" app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" app:layout_constraintEnd_toEndOf="@+id/panel" app:layout_constraintStart_toStartOf="@+id/panel" app:layout_constraintTop_toBottomOf="@+id/biometric_icon" app:layout_constraintVertical_bias="0.0" /> - <!-- Negative Button, reserved for app --> - <Button - android:id="@+id/button_negative" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:ellipsize="end" - android:maxLines="2" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <!-- Cancel Button, replaces negative button when biometric is accepted --> - <Button - android:id="@+id/button_cancel" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:text="@string/cancel" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <!-- "Use Credential" Button, replaces if device credential is allowed --> - <Button - android:id="@+id/button_use_credential" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <!-- Positive Button --> - <Button - android:id="@+id/button_confirm" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_confirm" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" - tools:visibility="invisible" /> - - <!-- Try Again Button --> - <Button - android:id="@+id/button_try_again" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_try_again" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" /> - - <!-- Guidelines for setting panel border --> <androidx.constraintlayout.widget.Barrier android:id="@+id/topBarrier" android:layout_width="wrap_content" @@ -269,21 +177,21 @@ android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" /> + app:constraint_referenced_ids="button_bar" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/leftGuideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_begin="0dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/rightGuideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_end="0dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/bottomGuideline" @@ -297,6 +205,29 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="horizontal" - app:layout_constraintGuide_percent="0.25171" /> + app:layout_constraintGuide_percent="0.25" /> + + <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper + android:id="@+id/biometric_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.8" + tools:srcCompat="@tools:sample/avatars" /> + + <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper + android:id="@+id/biometric_icon_overlay" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_gravity="center" + android:contentDescription="@null" + android:scaleType="fitXY" + app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" + app:layout_constraintEnd_toEndOf="@+id/biometric_icon" + app:layout_constraintStart_toStartOf="@+id/biometric_icon" + app:layout_constraintTop_toTopOf="@+id/biometric_icon" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index 8a19c2ebdcd6..4d207da851cd 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -100,7 +100,7 @@ app:layout_constraintStart_toStartOf="parent" android:transitionName="screenshot_preview_image"/> - <com.android.systemui.screenshot.CropView + <com.android.systemui.screenshot.scroll.CropView android:id="@+id/crop_view" android:layout_width="0px" android:layout_height="0px" @@ -122,7 +122,7 @@ tools:minHeight="100dp" tools:minWidth="100dp" /> - <com.android.systemui.screenshot.MagnifierView + <com.android.systemui.screenshot.scroll.MagnifierView android:id="@+id/magnifier" android:visibility="invisible" android:layout_width="200dp" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b7eff38aa015..76465c6e5011 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1087,7 +1087,7 @@ <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen> <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen> <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen> - <dimen name="biometric_dialog_corner_size">4dp</dimen> + <dimen name="biometric_dialog_corner_size">28dp</dimen> <!-- Y translation when showing/dismissing the dialog--> <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen> <dimen name="biometric_dialog_border_padding">4dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e3a5e156b055..774bbe504b03 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3358,7 +3358,7 @@ <string name="microphone_blocked_dream_overlay_content_description">Microphone blocked</string> <!-- Content description for priority mode icon on dream [CHAR LIMIT=NONE] --> - <string name="priority_mode_dream_overlay_content_description">Priority mode on</string> + <string name="priority_mode_dream_overlay_content_description">Do not disturb</string> <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] --> <string name="assistant_attention_content_description">User presence is detected</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 0bb5c174444f..fb331b62369e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -15,8 +15,10 @@ */ package com.android.keyguard; +import android.annotation.NonNull; import android.app.Presentation; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.media.MediaRouter; @@ -315,11 +317,11 @@ public class KeyguardDisplayManager { } @Override - public void onStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { // When concurrent state ends, the display also turns off. This is enforced in various // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke // hide() since that will happen through the onDisplayRemoved callback. - mIsInConcurrentDisplayState = state == mConcurrentState; + mIsInConcurrentDisplayState = state.getIdentifier() == mConcurrentState; } boolean isConcurrentDisplayActive(Display display) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 8bd675c44943..99cdc0181553 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -340,13 +340,19 @@ public class AuthContainerView extends LinearLayout mPanelInteractionDetector = panelInteractionDetector; mApplicationCoroutineScope = applicationCoroutineScope; + mPromptViewModel = promptViewModel; mTranslationY = getResources() .getDimension(R.dimen.biometric_dialog_animation_translation_offset); mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); + mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; + mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential( + config.mPromptInfo); + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - if (constraintBp()) { + if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo) + || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) { mLayout = (ConstraintLayout) layoutInflater.inflate( R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */); } else { @@ -375,9 +381,7 @@ public class AuthContainerView extends LinearLayout mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; mPromptCredentialInteractor = credentialInteractor; - mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; mCredentialViewModelProvider = credentialViewModelProvider; - mPromptViewModel = promptViewModel; mFpProps = fpProps; mFaceProps = faceProps; @@ -408,10 +412,6 @@ public class AuthContainerView extends LinearLayout @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper ) { - // Set this value before showing either of the prompt. - mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential( - config.mPromptInfo); - if (Utils.isBiometricAllowed(config.mPromptInfo) || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index 478ef8f296b2..1dfd2e5f9cc9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -23,6 +23,7 @@ import android.graphics.Outline import android.graphics.Rect import android.transition.AutoTransition import android.transition.TransitionManager +import android.util.TypedValue import android.view.Surface import android.view.View import android.view.ViewGroup @@ -54,13 +55,10 @@ import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall import com.android.systemui.biometrics.ui.viewmodel.isRight import com.android.systemui.biometrics.ui.viewmodel.isSmall import com.android.systemui.biometrics.ui.viewmodel.isTop -import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R -import kotlin.math.abs import kotlin.math.min import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** Helper for [BiometricViewBinder] to handle resize transitions. */ @@ -106,6 +104,13 @@ object BiometricViewSizeBinder { val iconHolderView = view.requireViewById<View>(R.id.biometric_icon) val panelView = view.requireViewById<View>(R.id.panel) val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size) + val cornerRadiusPx = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + cornerRadius, + view.resources.displayMetrics + ) + .toInt() // ConstraintSets for animating between prompt sizes val mediumConstraintSet = ConstraintSet() @@ -116,9 +121,10 @@ object BiometricViewSizeBinder { val largeConstraintSet = ConstraintSet() largeConstraintSet.clone(mediumConstraintSet) - largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0) - largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0) - largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0) + largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) + largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + largeConstraintSet.setVisibility(R.id.indicator, View.GONE) + largeConstraintSet.setVisibility(R.id.scrollView, View.GONE) // TODO: Investigate better way to handle 180 rotations val flipConstraintSet = ConstraintSet() @@ -141,7 +147,13 @@ object BiometricViewSizeBinder { panelView.outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect(0, 0, view.width, view.height, cornerRadius) + outline.setRoundRect( + 0, + 0, + view.width, + view.height + cornerRadiusPx, + cornerRadiusPx.toFloat() + ) } } @@ -164,35 +176,81 @@ object BiometricViewSizeBinder { when { position.isTop -> { + // Round bottom corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + -cornerRadiusPx, + view.width, + view.height, + cornerRadiusPx.toFloat() + ) + } + } left = windowBounds.centerX() - width / 2 + viewModel.promptMargin right = windowBounds.centerX() - width / 2 + viewModel.promptMargin bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4 } position.isBottom -> { - left = windowBounds.centerX() - width / 2 + viewModel.promptMargin - right = windowBounds.centerX() - width / 2 + viewModel.promptMargin - bottom = viewModel.promptMargin + // Round top corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + 0, + view.width, + view.height + cornerRadiusPx, + cornerRadiusPx.toFloat() + ) + } + } + + left = windowBounds.centerX() - width / 2 + right = windowBounds.centerX() - width / 2 + bottom = if (view.isLandscape()) bottomInset else 0 } position.isLeft -> { - left = viewModel.promptMargin - mid = - abs( - windowBounds.width() - iconHolderView.centerX() * 2 + - viewModel.promptMargin - ) + // Round right corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + -cornerRadiusPx, + 0, + view.width, + view.height, + cornerRadiusPx.toFloat() + ) + } + } + + left = 0 + mid = (windowBounds.width() * .85).toInt() / 2 right = windowBounds.width() - (windowBounds.width() * .85).toInt() - bottom = bottomInset + viewModel.promptMargin + bottom = if (view.isLandscape()) bottomInset else 0 } position.isRight -> { + // Round left corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + 0, + view.width + cornerRadiusPx, + view.height, + cornerRadiusPx.toFloat() + ) + } + } + left = windowBounds.width() - (windowBounds.width() * .85).toInt() - right = viewModel.promptMargin - bottom = bottomInset + viewModel.promptMargin - mid = - abs( - iconHolderView.centerX() - - (windowBounds.width() - iconHolderView.centerX()) - - viewModel.promptMargin - ) + right = 0 + bottom = if (view.isLandscape()) bottomInset else 0 + mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2 } } @@ -226,16 +284,8 @@ object BiometricViewSizeBinder { } } - fun setConstraintSetVisibility() { - viewsToHideWhenSmall.forEach { - mediumConstraintSet.setVisibility(it.id, it.showContentOrHide()) - largeConstraintSet.setVisibility(it.id, View.GONE) - smallConstraintSet.setVisibility(it.id, View.GONE) - } - - largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) - largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) - largeConstraintSet.setVisibility(R.id.indicator, View.GONE) + fun setVisibilities(size: PromptSize) { + viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) } if (viewModel.showBpWithoutIconForCredential.value) { smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) @@ -261,7 +311,7 @@ object BiometricViewSizeBinder { } measureBounds(position) - setConstraintSetVisibility() + setVisibilities(size) when { size.isSmall -> { val ratio = @@ -353,7 +403,7 @@ object BiometricViewSizeBinder { // prepare for animated size transitions for (v in viewsToHideWhenSmall) { - v.visibility = v.showContentOrHide(forceHide = size.isSmall) + v.showContentOrHide(forceHide = size.isSmall) } if (viewModel.showBpWithoutIconForCredential.value) { @@ -490,14 +540,15 @@ private fun View.isLandscape(): Boolean { } } -private fun View.showContentOrHide(forceHide: Boolean = false): Int { +private fun View.showContentOrHide(forceHide: Boolean = false) { val isTextViewWithBlankText = this is TextView && this.text.isBlank() val isImageViewWithoutImage = this is ImageView && this.drawable == null - return if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { - View.GONE - } else { - View.VISIBLE - } + visibility = + if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { + View.GONE + } else { + View.VISIBLE + } } private fun View.centerX(): Int { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt index e457601a6d52..d8265c7d40e8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt @@ -63,12 +63,6 @@ object PromptIconViewBinder { iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second - } else { - iconView.layoutParams.width = viewModel.fingerprintIconWidth.first() - iconView.layoutParams.height = viewModel.fingerprintIconWidth.first() - - iconOverlayView.layoutParams.width = viewModel.fingerprintIconWidth.first() - iconOverlayView.layoutParams.height = viewModel.fingerprintIconWidth.first() } var faceIcon: AnimatedVectorDrawable? = null @@ -84,10 +78,8 @@ object PromptIconViewBinder { } launch { - var width = 0 - var height = 0 - combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).collect { - (_, activeAuthType) -> + combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect { + (activeAuthType, iconSize) -> // Every time after bp shows, [isIconViewLoaded] is set to false in // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew // (when size or activeAuthType changes), we need to update @@ -109,8 +101,6 @@ object PromptIconViewBinder { } } AuthType.Face -> { - width = viewModel.faceIconWidth - height = viewModel.faceIconHeight /** * Set to true by default since face icon is a drawable, which * doesn't have a LottieOnCompositionLoadedListener equivalent. @@ -122,11 +112,12 @@ object PromptIconViewBinder { } } - if (width != 0 && height != 0) { - iconView.layoutParams.width = width - iconView.layoutParams.height = height - iconOverlayView.layoutParams.width = width - iconOverlayView.layoutParams.height = height + if (iconViewLayoutParamSizeOverride == null) { + iconView.layoutParams.width = iconSize.first + iconView.layoutParams.height = iconSize.second + + iconOverlayView.layoutParams.width = iconSize.first + iconOverlayView.layoutParams.height = iconSize.second } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt index 257eb4a60328..d0c140b353e2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt @@ -21,6 +21,7 @@ import android.annotation.DrawableRes import android.annotation.RawRes import android.content.res.Configuration import android.graphics.Rect +import android.hardware.face.Face import android.util.RotationUtils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor @@ -65,9 +66,10 @@ constructor( */ val activeAuthType: Flow<AuthType> = combine( + promptViewModel.size, promptViewModel.modalities.distinctUntilChanged(), promptViewModel.faceMode.distinctUntilChanged() - ) { modalities, faceMode -> + ) { _, modalities, faceMode -> if (modalities.hasFaceAndFingerprint && !faceMode) { AuthType.Coex } else if (modalities.hasFaceOnly || faceMode) { @@ -158,13 +160,18 @@ constructor( /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow() - /** Layout params for fingerprint iconView */ - val fingerprintIconWidth: Flow<Int> = promptViewModel.fingerprintSensorDiameter - val fingerprintIconHeight: Flow<Int> = promptViewModel.fingerprintSensorDiameter - - /** Layout params for face iconView */ - val faceIconWidth: Int = promptViewModel.faceIconWidth - val faceIconHeight: Int = promptViewModel.faceIconHeight + val iconSize: Flow<Pair<Int, Int>> = + combine( + activeAuthType, + promptViewModel.fingerprintSensorWidth, + promptViewModel.fingerprintSensorHeight, + ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight -> + if (activeAuthType == AuthType.Face) { + Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight) + } else { + Pair(fingerprintSensorWidth, fingerprintSensorHeight) + } + } /** Current BiometricPromptLayout.iconView asset. */ val iconAsset: Flow<Int> = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 86b0b4455f61..fbd87fd3a9f5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -86,7 +86,7 @@ constructor( val faceIconHeight: Int = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) - val fingerprintSensorDiameter: Flow<Int> = + val fingerprintSensorWidth: Flow<Int> = combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams -> if (modalities.hasUdfps) { @@ -96,6 +96,16 @@ constructor( } } + val fingerprintSensorHeight: Flow<Int> = + combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams + -> + if (modalities.hasUdfps) { + overlayParams.sensorBounds.height() + } else { + fingerprintIconHeight + } + } + private val _accessibilityHint = MutableSharedFlow<String>() /** Hint for talkback directional guidance */ @@ -342,7 +352,7 @@ constructor( position, message, ) { size, _, message -> - size.isNotSmall && message.message.isNotBlank() + size.isMedium && message.message.isNotBlank() } /** If the auth is pending confirmation and the confirm button should be shown. */ diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index 9afd5ede0b4c..d2df276002cc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -24,9 +24,9 @@ import com.android.systemui.contrast.ContrastDialogActivity; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; -import com.android.systemui.screenshot.LongScreenshotActivity; import com.android.systemui.screenshot.appclips.AppClipsActivity; import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity; +import com.android.systemui.screenshot.scroll.LongScreenshotActivity; import com.android.systemui.sensorprivacy.SensorUseStartedActivity; import com.android.systemui.settings.brightness.BrightnessDialog; import com.android.systemui.telephony.ui.activity.SwitchToManagedProfileForCallActivity; diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt index 83337f760c33..fa7603ff777c 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt @@ -62,7 +62,7 @@ constructor( conflatedCallbackFlow { val callback = DeviceStateManager.DeviceStateCallback { state -> - trySend(deviceStateToPosture(state)) + trySend(deviceStateToPosture(state.identifier)) } deviceStateManager.registerCallback(executor, callback) awaitClose { deviceStateManager.unregisterCallback(callback) } diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index 1d820a14be4e..5f0635bdba01 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -21,6 +21,9 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS; import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN; +import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD; +import static android.appwidget.flags.Flags.generatedPreviews; import static android.content.Intent.ACTION_BOOT_COMPLETED; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; @@ -80,12 +83,15 @@ import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.text.TextUtils; import android.util.Log; +import android.util.SparseBooleanArray; import android.widget.RemoteViews; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; @@ -96,6 +102,8 @@ import com.android.systemui.people.PeopleBackupFollowUpJob; import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.people.PeopleTileViewHelper; import com.android.systemui.people.SharedPreferencesHelper; +import com.android.systemui.res.R; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -160,13 +168,27 @@ public class PeopleSpaceWidgetManager implements Dumpable { @GuardedBy("mLock") public static Map<Integer, PeopleSpaceTile> mTiles = new HashMap<>(); + @NonNull private final UserTracker mUserTracker; + @NonNull private final SparseBooleanArray mUpdatedPreviews = new SparseBooleanArray(); + @NonNull private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onUserUnlocked() { + if (DEBUG) { + Log.d(TAG, "onUserUnlocked " + mUserTracker.getUserId()); + } + updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); + } + }; + @Inject public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps, CommonNotifCollection notifCollection, PackageManager packageManager, Optional<Bubbles> bubblesOptional, UserManager userManager, NotificationManager notificationManager, BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor, - DumpManager dumpManager) { + DumpManager dumpManager, @NonNull UserTracker userTracker, + @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor) { if (DEBUG) Log.d(TAG, "constructor"); mContext = context; mAppWidgetManager = AppWidgetManager.getInstance(context); @@ -187,6 +209,8 @@ public class PeopleSpaceWidgetManager implements Dumpable { mBroadcastDispatcher = broadcastDispatcher; mBgExecutor = bgExecutor; dumpManager.registerNormalDumpable(TAG, this); + mUserTracker = userTracker; + keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); } /** Initializes {@PeopleSpaceWidgetManager}. */ @@ -246,7 +270,7 @@ public class PeopleSpaceWidgetManager implements Dumpable { CommonNotifCollection notifCollection, PackageManager packageManager, Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager, INotificationManager iNotificationManager, NotificationManager notificationManager, - @Background Executor executor) { + @Background Executor executor, UserTracker userTracker) { mContext = context; mAppWidgetManager = appWidgetManager; mIPeopleManager = iPeopleManager; @@ -262,6 +286,7 @@ public class PeopleSpaceWidgetManager implements Dumpable { mManager = this; mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); mBgExecutor = executor; + mUserTracker = userTracker; } /** @@ -1407,4 +1432,24 @@ public class PeopleSpaceWidgetManager implements Dumpable { Trace.traceEnd(Trace.TRACE_TAG_APP); } + + @VisibleForTesting + void updateGeneratedPreviewForUser(UserHandle user) { + if (!generatedPreviews() || mUpdatedPreviews.get(user.getIdentifier()) + || !mUserManager.isUserUnlocked(user)) { + return; + } + if (DEBUG) { + Log.d(TAG, "Updating People Space widget preview for user " + user.getIdentifier()); + } + boolean success = mAppWidgetManager.setWidgetPreview( + new ComponentName(mContext, PeopleSpaceWidgetProvider.class), + WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD, + new RemoteViews(mContext.getPackageName(), + R.layout.people_space_placeholder_layout)); + if (DEBUG && !success) { + Log.d(TAG, "Failed to update generated preview for user " + user.getIdentifier()); + } + mUpdatedPreviews.put(user.getIdentifier(), success); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt index aed08f8b5457..4a8e33a99b25 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt @@ -37,7 +37,11 @@ sealed class TileSpec private constructor(open val spec: String) { data class PlatformTileSpec internal constructor( override val spec: String, - ) : TileSpec(spec) + ) : TileSpec(spec) { + override fun toString(): String { + return "P($spec)" + } + } /** * Container for the spec of a tile provided by an app. @@ -50,7 +54,7 @@ sealed class TileSpec private constructor(open val spec: String) { val componentName: ComponentName, ) : TileSpec(spec) { override fun toString(): String { - return "CustomTileSpec(${componentName.toShortString()})" + return "C(${componentName.flattenToShortString()})" } } diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java index 9076182def70..f02b871b196d 100644 --- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java @@ -16,12 +16,14 @@ package com.android.systemui.reardisplay; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManagerGlobal; import android.view.LayoutInflater; @@ -29,7 +31,6 @@ import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.LinearLayout; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -179,6 +180,7 @@ public class RearDisplayDialogController implements */ private void initializeValues(int startingBaseState) { mRearDisplayEducationDialog = mSystemUIDialogFactory.create(); + // TODO(b/329170810): Refactor and remove with updated DeviceStateManager values. if (mFoldedStates == null) { mFoldedStates = mResources.getIntArray( com.android.internal.R.array.config_foldedDeviceStates); @@ -228,21 +230,19 @@ public class RearDisplayDialogController implements private class DeviceStateManagerCallback implements DeviceStateManager.DeviceStateCallback { @Override - public void onBaseStateChanged(int state) { - if (mStartedFolded && !isFoldedState(state)) { + public void onDeviceStateChanged(@NonNull DeviceState state) { + if (mStartedFolded && !state.hasProperty( + DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) { // We've opened the device, we can close the overlay mRearDisplayEducationDialog.dismiss(); closeOverlayAndNotifyService(false); - } else if (!mStartedFolded && isFoldedState(state)) { + } else if (!mStartedFolded && state.hasProperty( + DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) { // We've closed the device, finish activity mRearDisplayEducationDialog.dismiss(); closeOverlayAndNotifyService(true); } } - - // We only care about physical device changes in this scenario - @Override - public void onStateChanged(int state) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index c4287ca899dc..864f29a5c5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -126,7 +126,7 @@ public class ImageExporter { /** * Writes the given Bitmap to outputFile. */ - ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap, + public ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap, final File outputFile) { return CallbackToFutureAdapter.getFuture( (completer) -> { @@ -196,7 +196,7 @@ public class ImageExporter { * @param bitmap the bitmap to export * @return a listenable future result */ - ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, + public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, UserHandle owner, int displayId) { return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality, /* publish */ true, owner, mFlags, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index 1f6d2122ebfc..a1481f6d6d2b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -34,6 +34,7 @@ import androidx.appcompat.content.res.AppCompatResources import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R +import com.android.systemui.screenshot.scroll.ScrollCaptureController import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER import dagger.assisted.Assisted diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java index 6050c2b90e34..440cf1c344da 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java @@ -16,8 +16,9 @@ package com.android.systemui.screenshot; +/** Stores debug log configuration for screenshots. */ @SuppressWarnings("PointlessBooleanExpression") -class LogConfig { +public class LogConfig { /** Log ALL the things... */ private static final boolean DEBUG_ALL = false; @@ -29,36 +30,37 @@ class LogConfig { private static final boolean TAG_WITH_CLASS_NAME = false; /** Action creation and user selection: Share, Save, Edit, Delete, Smart action, etc */ - static final boolean DEBUG_ACTIONS = DEBUG_ALL || false; + public static final boolean DEBUG_ACTIONS = DEBUG_ALL || false; /** Debug info about animations such as start, complete and cancel */ - static final boolean DEBUG_ANIM = DEBUG_ALL || false; + public static final boolean DEBUG_ANIM = DEBUG_ALL || false; /** Whenever Uri is supplied to consumer, or onComplete runnable is run() */ - static final boolean DEBUG_CALLBACK = DEBUG_ALL || false; + public static final boolean DEBUG_CALLBACK = DEBUG_ALL || false; /** Logs information about dismissing the screenshot tool */ - static final boolean DEBUG_DISMISS = DEBUG_ALL || false; + public static final boolean DEBUG_DISMISS = DEBUG_ALL || false; /** Touch or key event driven action or side effects */ - static final boolean DEBUG_INPUT = DEBUG_ALL || false; + public static final boolean DEBUG_INPUT = DEBUG_ALL || false; /** Scroll capture usage */ - static final boolean DEBUG_SCROLL = DEBUG_ALL || false; + public static final boolean DEBUG_SCROLL = DEBUG_ALL || false; /** Service lifecycle events and callbacks */ - static final boolean DEBUG_SERVICE = DEBUG_ALL || false; + public static final boolean DEBUG_SERVICE = DEBUG_ALL || false; /** Storage related actions, Bitmap.compress, ContentManager, etc */ - static final boolean DEBUG_STORAGE = DEBUG_ALL || false; + public static final boolean DEBUG_STORAGE = DEBUG_ALL || false; /** High level logical UI actions: timeout, onConfigChanged, insets, show actions, reset */ - static final boolean DEBUG_UI = DEBUG_ALL || false; + public static final boolean DEBUG_UI = DEBUG_ALL || false; /** Interactions with Window and WindowManager */ - static final boolean DEBUG_WINDOW = DEBUG_ALL || false; + public static final boolean DEBUG_WINDOW = DEBUG_ALL || false; - static String logTag(Class<?> cls) { + /** Get the appropriate class name */ + public static String logTag(Class<?> cls) { return TAG_WITH_CLASS_NAME ? cls.getSimpleName() : TAG_SS; } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 198a29c4ed5b..c8e13bb8c2fc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -87,6 +87,10 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; +import com.android.systemui.screenshot.scroll.LongScreenshotActivity; +import com.android.systemui.screenshot.scroll.LongScreenshotData; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient; +import com.android.systemui.screenshot.scroll.ScrollCaptureController; import com.android.systemui.util.Assert; import com.google.common.util.concurrent.ListenableFuture; @@ -200,7 +204,7 @@ public class ScreenshotController { void onActionsReady(ScreenshotController.QuickShareData quickShareData); } - interface TransitionDestination { + public interface TransitionDestination { /** * Allows the long screenshot activity to call back with a destination location (the bounds * on screen of the destination for the transitioning view) and a Runnable to be run once diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 1c5a8a1a9fd3..cb2dba00890b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -92,6 +92,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.screenshot.scroll.ScrollCaptureController; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index 182b8894677a..6be32a97f4e2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -25,6 +25,7 @@ import android.view.ScrollCaptureResponse import android.view.View import android.view.ViewGroup import android.view.WindowInsets +import com.android.systemui.screenshot.scroll.ScrollCaptureController /** Abstraction of the surface between ScreenshotController and ScreenshotView */ interface ScreenshotViewProxy { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java index aa23d6bed5e1..d87d85b93b9d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java @@ -52,7 +52,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger.UiEventEnum; import com.android.settingslib.Utils; import com.android.systemui.res.R; -import com.android.systemui.screenshot.CropView; +import com.android.systemui.screenshot.scroll.CropView; import com.android.systemui.settings.UserTracker; import javax.inject.Inject; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java index 2f411ea3cb33..5e561cfb14a1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.animation.ValueAnimator; import android.content.Context; @@ -265,7 +265,7 @@ public class CropView extends View { Log.w(TAG, "No boundary selected"); break; } - Log.i(TAG, "Updated mCrop: " + mCrop); + Log.i(TAG, "Updated mCrop: " + mCrop); invalidate(); } @@ -385,7 +385,7 @@ public class CropView extends View { /** * @param action either ACTION_DOWN, ACTION_UP or ACTION_MOVE. - * @param x coordinate of the relevant pointer. + * @param x x-coordinate of the relevant pointer. */ private void updateListener(int action, float x) { if (mCropInteractionListener != null && isVertical(mCurrentDraggingBoundary)) { @@ -643,11 +643,13 @@ public class CropView extends View { /** * Listen for crop motion events and state. */ - public interface CropInteractionListener { + interface CropInteractionListener { void onCropDragStarted(CropBoundary boundary, float boundaryPosition, int boundaryPositionPx, float horizontalCenter, float x); + void onCropDragMoved(CropBoundary boundary, float boundaryPosition, int boundaryPositionPx, float horizontalCenter, float x); + void onCropDragComplete(); } @@ -675,8 +677,7 @@ public class CropView extends View { out.writeParcelable(mCrop, 0); } - public static final Parcelable.Creator<SavedState> CREATOR - = new Parcelable.Creator<SavedState>() { + public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java index 7ee7c319799c..df86d69b2527 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.annotation.Nullable; import android.content.ContentResolver; @@ -35,20 +35,20 @@ import java.io.InputStream; import javax.inject.Inject; /** Loads images. */ -public class ImageLoader { +class ImageLoader { private final ContentResolver mResolver; static class Result { - @Nullable Uri uri; - @Nullable File fileName; - @Nullable Bitmap bitmap; + @Nullable Uri mUri; + @Nullable File mFilename; + @Nullable Bitmap mBitmap; @Override public String toString() { final StringBuilder sb = new StringBuilder("Result{"); - sb.append("uri=").append(uri); - sb.append(", fileName=").append(fileName); - sb.append(", bitmap=").append(bitmap); + sb.append("uri=").append(mUri); + sb.append(", fileName=").append(mFilename); + sb.append(", bitmap=").append(mBitmap); sb.append('}'); return sb.toString(); } @@ -69,11 +69,10 @@ public class ImageLoader { return CallbackToFutureAdapter.getFuture(completer -> { Result result = new Result(); try (InputStream in = mResolver.openInputStream(uri)) { - result.uri = uri; - result.bitmap = BitmapFactory.decodeStream(in); + result.mUri = uri; + result.mBitmap = BitmapFactory.decodeStream(in); completer.set(result); - } - catch (IOException e) { + } catch (IOException e) { completer.setException(e); } return "BitmapFactory#decodeStream"; @@ -91,8 +90,8 @@ public class ImageLoader { return CallbackToFutureAdapter.getFuture(completer -> { try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { Result result = new Result(); - result.fileName = file; - result.bitmap = BitmapFactory.decodeStream(in); + result.mFilename = file; + result.mBitmap = BitmapFactory.decodeStream(in); completer.set(result); } catch (IOException e) { completer.setException(e); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java index a95c91bfeceb..c9c297e53bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static android.graphics.ColorSpace.Named.SRGB; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java index 356f67e7ea00..76a72f7e4adf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.annotation.AnyThread; import android.graphics.Bitmap; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java index 00d480a76355..1e1a577ebd4a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.app.Activity; import android.app.ActivityOptions; @@ -47,11 +47,14 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.view.OneShotPreDrawListener; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; -import com.android.systemui.screenshot.CropView.CropBoundary; -import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; -import com.android.systemui.settings.UserTracker; +import com.android.systemui.screenshot.ActionIntentCreator; +import com.android.systemui.screenshot.ActionIntentExecutor; +import com.android.systemui.screenshot.ImageExporter; +import com.android.systemui.screenshot.LogConfig; +import com.android.systemui.screenshot.ScreenshotEvent; +import com.android.systemui.screenshot.scroll.CropView.CropBoundary; +import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot; import com.google.common.util.concurrent.ListenableFuture; @@ -81,8 +84,6 @@ public class LongScreenshotActivity extends Activity { private final ImageExporter mImageExporter; private final LongScreenshotData mLongScreenshotHolder; private final ActionIntentExecutor mActionExecutor; - private final FeatureFlags mFeatureFlags; - private final UserTracker mUserTracker; private ImageView mPreview; private ImageView mTransitionView; @@ -113,16 +114,13 @@ public class LongScreenshotActivity extends Activity { @Inject public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter, @Main Executor mainExecutor, @Background Executor bgExecutor, - LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor, - FeatureFlags featureFlags, UserTracker userTracker) { + LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor) { mUiEventLogger = uiEventLogger; mUiExecutor = mainExecutor; mBackgroundExecutor = bgExecutor; mImageExporter = imageExporter; mLongScreenshotHolder = longScreenshotHolder; mActionExecutor = actionExecutor; - mFeatureFlags = featureFlags; - mUserTracker = userTracker; } @@ -265,13 +263,13 @@ public class LongScreenshotActivity extends Activity { private void onCachedImageLoaded(ImageLoader.Result imageResult) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED); - BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap); + BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.mBitmap); mPreview.setImageDrawable(drawable); mPreview.setAlpha(1f); - mMagnifierView.setDrawable(drawable, imageResult.bitmap.getWidth(), - imageResult.bitmap.getHeight()); + mMagnifierView.setDrawable(drawable, imageResult.mBitmap.getWidth(), + imageResult.mBitmap.getHeight()); mCropView.setVisibility(View.VISIBLE); - mSavedImagePath = imageResult.fileName; + mSavedImagePath = imageResult.mFilename; setButtonsEnabled(true); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java index f549faf2414a..ebac5bf2debd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.screenshot.ScreenshotController; import java.util.concurrent.atomic.AtomicReference; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java index 0c543cd8909c..0a1a74735648 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -64,16 +64,16 @@ public class MagnifierView extends View implements CropView.CropInteractionListe private ViewPropertyAnimator mTranslationAnimator; private final Animator.AnimatorListener mTranslationAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - mTranslationAnimator = null; - } - - @Override - public void onAnimationEnd(Animator animation) { - mTranslationAnimator = null; - } - }; + @Override + public void onAnimationCancel(Animator animation) { + mTranslationAnimator = null; + } + + @Override + public void onAnimationEnd(Animator animation) { + mTranslationAnimator = null; + } + }; public MagnifierView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java index e93f737308ba..0e4334314ec2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; @@ -46,6 +46,7 @@ import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.screenshot.LogConfig; import com.google.common.util.concurrent.ListenableFuture; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java index 8a2678c8ab09..f4c77da674b0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.content.Context; import android.graphics.Bitmap; @@ -30,8 +30,10 @@ import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; -import com.android.systemui.screenshot.ScrollCaptureClient.Session; +import com.android.systemui.screenshot.LogConfig; +import com.android.systemui.screenshot.ScreenshotEvent; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session; import com.google.common.util.concurrent.ListenableFuture; @@ -85,7 +87,7 @@ public class ScrollCaptureController { mImageTileSet = imageTileSet; } - /** Returns a bitmap containing the combinded result. */ + /** Returns a bitmap containing the combined result. */ public Bitmap toBitmap() { return mImageTileSet.toBitmap(); } @@ -167,7 +169,7 @@ public class ScrollCaptureController { * {@link ScrollCaptureResponse#isConnected() connected}. * @return a future ImageTile set containing the result */ - ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) { + public ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) { mCancelled = false; return CallbackToFutureAdapter.getFuture(completer -> { mCaptureCompleter = completer; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java index 71df369aa7b8..00455bcfbd99 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.annotation.Nullable; import android.graphics.Canvas; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt index 56b0d598a512..db237e89a683 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone import android.content.Context +import android.hardware.devicestate.DeviceState import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback import com.android.internal.R @@ -45,13 +46,13 @@ internal class FoldStateListener( private var wasFolded: Boolean? = null - override fun onStateChanged(state: Int) { - val isFolded = foldedDeviceStates.contains(state) + override fun onDeviceStateChanged(state: DeviceState) { + val isFolded = foldedDeviceStates.contains(state.identifier) if (wasFolded == isFolded) { return } wasFolded = isFolded - val willGoToSleep = goToSleepDeviceStates.contains(state) + val willGoToSleep = goToSleepDeviceStates.contains(state.identifier) listener.onFoldStateChanged(isFolded, willGoToSleep) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java index 422aa4d4aa60..de0eb493c83d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java @@ -16,12 +16,13 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateUtil; import android.util.SparseIntArray; -import androidx.annotation.NonNull; - import com.android.app.tracing.ListenersTracing; import com.android.internal.R; import com.android.systemui.dagger.SysUISingleton; @@ -42,8 +43,9 @@ public class DevicePostureControllerImpl implements DevicePostureController { /** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */ private static final int COMMON_STATE_USE_BASE_STATE = 1000; private final List<Callback> mListeners = new ArrayList<>(); + private final List<DeviceState> mSupportedStates; + private DeviceState mCurrentDeviceState; private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN; - private int mCurrentBasePosture = DEVICE_POSTURE_UNKNOWN; private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); @@ -76,21 +78,17 @@ public class DevicePostureControllerImpl implements DevicePostureController { mDeviceStateToPostureMap.put(deviceState, posture); } + mSupportedStates = deviceStateManager.getSupportedDeviceStates(); deviceStateManager.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() { @Override - public void onStateChanged(int state) { - Assert.isMainThread(); - mCurrentDevicePosture = - mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); - sendUpdatePosture(); - } - - @Override - public void onBaseStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { + mCurrentDeviceState = state; Assert.isMainThread(); - mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); - - if (useBaseState()) { + int newDevicePosture = + mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN); + if (newDevicePosture != mCurrentDevicePosture + || newDevicePosture == COMMON_STATE_USE_BASE_STATE) { + mCurrentDevicePosture = newDevicePosture; sendUpdatePosture(); } } @@ -120,7 +118,8 @@ public class DevicePostureControllerImpl implements DevicePostureController { @Override public int getDevicePosture() { if (useBaseState()) { - return mCurrentBasePosture; + return DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState, + mSupportedStates); } else { return mCurrentDevicePosture; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index 3008c866d207..88cf46a0ca07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -20,6 +20,7 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORE import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; import android.annotation.Nullable; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.os.Trace; import android.util.IndentingPrintWriter; @@ -120,18 +121,18 @@ public final class DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked); } - private void updateDeviceState(int state) { - mLogger.logUpdateDeviceState(mDeviceState, state); - if (Trace.isEnabled()) { - Trace.traceBegin( - Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]"); - } + private void updateDeviceState(@NonNull DeviceState state) { + mLogger.logUpdateDeviceState(mDeviceState, state.getIdentifier()); try { - if (mDeviceState == state) { + if (Trace.isEnabled()) { + Trace.traceBegin(Trace.TRACE_TAG_APP, + "updateDeviceState [state=" + state.getIdentifier() + "]"); + } + if (mDeviceState == state.getIdentifier()) { return; } - readPersistedSetting("updateDeviceState", state); + readPersistedSetting("updateDeviceState", state.getIdentifier()); } finally { Trace.endSection(); } diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 5882b56fff6a..572a6c12f3e1 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -113,7 +113,7 @@ android:excludeFromRecents="true" /> - <activity android:name="com.android.systemui.screenshot.ScrollViewActivity" + <activity android:name="com.android.systemui.screenshot.scroll.ScrollViewActivity" android:exported="false" /> <activity android:name="com.android.systemui.screenshot.RecyclerViewActivity" diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 90587d7386ce..f1dfdf42ed0e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -20,16 +20,18 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; -import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; @@ -46,6 +48,7 @@ import org.mockito.MockitoAnnotations; public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { + private KosmosJavaAdapter mKosmos; @Mock protected KeyguardStatusView mKeyguardStatusView; @Mock protected KeyguardSliceViewController mKeyguardSliceViewController; @@ -57,7 +60,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected ScreenOffAnimationController mScreenOffAnimationController; @Mock protected KeyguardLogger mKeyguardLogger; @Mock protected KeyguardStatusViewController mControllerMock; - @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; @Mock protected DumpManager mDumpManager; protected FakeKeyguardRepository mFakeKeyguardRepository; @@ -71,6 +73,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Before public void setup() { + mKosmos = new KosmosJavaAdapter(this); MockitoAnnotations.initMocks(this); KeyguardInteractorFactory.WithDependencies deps = KeyguardInteractorFactory.create(); @@ -87,7 +90,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mInteractionJankMonitor, + mKosmos.getInteractionJankMonitor(), deps.getKeyguardInteractor(), mDumpManager, PowerInteractorFactory.create( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 336a97ef09f4..fde45d34a4fd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -135,6 +135,7 @@ import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStat import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus; import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus; import com.android.systemui.dump.DumpManager; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; @@ -195,7 +196,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID, TEST_CARRIER_ID, PROFILE_CLASS_PROVISIONING); private static final int FINGERPRINT_SENSOR_ID = 1; - + private KosmosJavaAdapter mKosmos; @Mock private UserTracker mUserTracker; @Mock @@ -240,7 +241,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private AuthController mAuthController; @Mock private TelephonyListenerManager mTelephonyListenerManager; - @Mock private InteractionJankMonitor mInteractionJankMonitor; @Mock private LatencyTracker mLatencyTracker; @@ -300,6 +300,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Before public void setup() throws RemoteException { + mKosmos = new KosmosJavaAdapter(this); + mInteractionJankMonitor = mKosmos.getInteractionJankMonitor(); MockitoAnnotations.initMocks(this); when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt index 96ce3abebeaf..b73e4e6ab015 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt @@ -18,6 +18,8 @@ import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.DecorView import com.android.systemui.SysuiTestCase +import com.android.systemui.jank.interactionJankMonitor +import com.android.systemui.kosmos.Kosmos import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse @@ -31,7 +33,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -43,7 +44,7 @@ class DialogTransitionAnimatorTest : SysuiTestCase() { private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator private val attachedViews = mutableSetOf<View>() - @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + val interactionJankMonitor = Kosmos().interactionJankMonitor @get:Rule val rule = MockitoJUnit.rule() @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 416b33458cdd..072569d0e69b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -399,6 +399,7 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test @Ignore("b/302735104") fun testShowCredentialUI_withCustomBp() { + mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, isUsingContentView = true diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt index 21b8aca363ca..c79cbab87576 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt @@ -85,7 +85,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_FOLDED) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_FOLDED) + ) assertThat(state()).isEqualTo(DeviceState.FOLDED) } @@ -95,7 +97,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_HALF_FOLDED) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_HALF_FOLDED) + ) assertThat(state()).isEqualTo(DeviceState.HALF_FOLDED) } @@ -105,7 +109,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_UNFOLDED) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_UNFOLDED) + ) assertThat(state()).isEqualTo(DeviceState.UNFOLDED) } @@ -115,7 +121,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_REAR_DISPLAY) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_REAR_DISPLAY) + ) assertThat(state()).isEqualTo(DeviceState.REAR_DISPLAY) } @@ -125,7 +133,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_CONCURRENT_DISPLAY) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_CONCURRENT_DISPLAY) + ) assertThat(state()).isEqualTo(DeviceState.CONCURRENT_DISPLAY) } @@ -135,7 +145,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(123456) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(123456) + ) assertThat(state()).isEqualTo(DeviceState.UNKNOWN) } @@ -152,6 +164,13 @@ class DeviceStateRepositoryTest : SysuiTestCase() { private fun Int.toIntArray() = listOf(this).toIntArray() + private fun getDeviceStateForIdentifier(id: Int): android.hardware.devicestate.DeviceState { + return android.hardware.devicestate.DeviceState( + android.hardware.devicestate.DeviceState.Configuration.Builder(id, /* name= */ "") + .build() + ) + } + private companion object { // Used to fake the ids in the test. Note that there is no guarantees different devices will // have the same ids (that's why the ones in this test start from 41) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 184924596341..272b48876a37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -75,7 +75,6 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.foldables.FoldGracePeriodProvider; -import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; @@ -181,7 +180,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock NotificationShadeDepthController mNotificationShadeDepthController; private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock ScreenOffAnimationController mScreenOffAnimationController; - private @Mock InteractionJankMonitor mInteractionJankMonitor; private @Mock ScreenOnCoordinator mScreenOnCoordinator; private @Mock KeyguardTransitions mKeyguardTransitions; private @Mock ShadeController mShadeController; @@ -235,8 +233,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager); when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class)); when(mPowerManager.isInteractive()).thenReturn(true); - when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true); - when(mInteractionJankMonitor.end(anyInt())).thenReturn(true); mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); final ViewRootImpl testViewRoot = mock(ViewRootImpl.class); when(testViewRoot.getView()).thenReturn(mock(View.class)); @@ -1245,7 +1241,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mNotificationShadeDepthController, mScreenOnCoordinator, mKeyguardTransitions, - mInteractionJankMonitor, + mKosmos.getInteractionJankMonitor(), mDreamOverlayStateController, mJavaAdapter, mWallpaperRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index a63b2211f71a..d1d9efc10ea7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -101,11 +101,12 @@ import android.text.TextUtils; import androidx.preference.PreferenceManager; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.people.PeopleBackupFollowUpJob; import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.people.SharedPreferencesHelper; +import com.android.systemui.res.R; +import com.android.systemui.settings.FakeUserTracker; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.SbnBuilder; @@ -265,6 +266,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private final FakeExecutor mFakeExecutor = new FakeExecutor(mClock); + private final FakeUserTracker mUserTracker = new FakeUserTracker(); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -272,7 +275,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager, mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager, Optional.of(mBubbles), mUserManager, mBackupManager, mINotificationManager, - mNotificationManager, mFakeExecutor); + mNotificationManager, mFakeExecutor, mUserTracker); mManager.attach(mListenerService); verify(mListenerService).addNotificationHandler(mListenerCaptor.capture()); @@ -1562,6 +1565,43 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS)); } + @Test + public void testUpdateGeneratedPreview_flagDisabled() { + mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); + mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); + verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any()); + } + + @Test + public void testUpdateGeneratedPreview_userLocked() { + mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); + when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false); + + mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); + verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any()); + } + + @Test + public void testUpdateGeneratedPreview_userUnlocked() { + mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); + when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); + when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); + + mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); + verify(mAppWidgetManager, times(1)).setWidgetPreview(any(), anyInt(), any()); + } + + @Test + public void testUpdateGeneratedPreview_doesNotSetTwice() { + mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); + when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); + when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); + + mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); + mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); + verify(mAppWidgetManager, times(1)).setWidgetPreview(any(), anyInt(), any()); + } + private void setFinalField(String fieldName, int value) { try { Field field = NotificationManager.Policy.class.getDeclaredField(fieldName); diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java index dc211303e52c..f88a5a0d9f41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java @@ -19,7 +19,6 @@ package com.android.systemui.reardisplay; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotSame; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.reset; @@ -28,6 +27,7 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.content.res.Resources; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -38,9 +38,7 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.res.R; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.res.R; import com.android.systemui.statusbar.CommandQueue; @@ -176,6 +174,6 @@ public class RearDisplayDialogControllerTest extends SysuiTestCase { DeviceStateManager.DeviceStateCallback { @Override - public void onStateChanged(int state) { } + public void onDeviceStateChanged(DeviceState state) { } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java index 4c8a4b0f8f61..aad461392cf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static com.google.common.util.concurrent.Futures.getUnchecked; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java index 670a130d610a..10232602b655 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static org.junit.Assert.assertEquals; @@ -37,8 +37,8 @@ import android.view.ScrollCaptureResponse; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; -import com.android.systemui.screenshot.ScrollCaptureClient.Session; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session; import com.google.common.util.concurrent.ListenableFuture; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java index 6f081c759df7..f39f5439d4ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static com.google.common.util.concurrent.Futures.getUnchecked; import static com.google.common.util.concurrent.Futures.immediateFuture; @@ -36,7 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; -import com.android.systemui.screenshot.ScrollCaptureClient.Session; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java index de97bc36be56..5699cfc96c8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java index 4c84df2769a0..04aba1133a78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.app.Activity; import android.os.Bundle; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 3ed8b28bd780..b9451bafec90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -17,7 +17,6 @@ package com.android.systemui.shade; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -290,10 +289,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { when(mNotificationRemoteInputManager.isRemoteInputActive()) .thenReturn(false); - when(mInteractionJankMonitor.begin(any(), anyInt())) - .thenReturn(true); - when(mInteractionJankMonitor.end(anyInt())) - .thenReturn(true); when(mPanelView.getParent()).thenReturn(mPanelViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index c8c54dbd4ac2..611cf91813ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -49,6 +49,7 @@ import android.app.WallpaperManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.IntentFilter; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.fingerprint.FingerprintManager; @@ -1113,10 +1114,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } private void setDeviceState(int state) { + DeviceState deviceState = new DeviceState( + new DeviceState.Configuration.Builder(state, "TEST").build()); ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor = ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); verify(mDeviceStateManager).registerCallback(any(), callbackCaptor.capture()); - callbackCaptor.getValue().onStateChanged(state); + callbackCaptor.getValue().onDeviceStateChanged(deviceState); } private void setGoToSleepStates(int... states) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt index 649dc235f398..5d42d5167c27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.phone +import android.hardware.devicestate.DeviceState import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.R @@ -40,52 +41,52 @@ class FoldStateListenerTest : SysuiTestCase() { @Before fun setUp() { initMocks(this) - setFoldedStates(DEVICE_STATE_FOLDED) - setGoToSleepStates(DEVICE_STATE_FOLDED) + setFoldedStates(DEVICE_STATE_FOLDED.identifier) + setGoToSleepStates(DEVICE_STATE_FOLDED.identifier) sut = FoldStateListener(mContext, listener) } @Test fun onStateChanged_stateFolded_notifiesWithFoldedAndGoingToSleep() { - sut.onStateChanged(DEVICE_STATE_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_FOLDED) verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) } @Test fun onStateChanged_stateHalfFolded_notifiesWithNotFoldedAndNotGoingToSleep() { - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateUnfolded_notifiesWithNotFoldedAndNotGoingToSleep() { - sut.onStateChanged(DEVICE_STATE_UNFOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED) verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateUnfoldedThenHalfFolded_notifiesOnce() { - sut.onStateChanged(DEVICE_STATE_UNFOLDED) - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateHalfFoldedThenUnfolded_notifiesOnce() { - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) - sut.onStateChanged(DEVICE_STATE_UNFOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED) verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateHalfFoldedThenFolded_notifiesTwice() { - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) - sut.onStateChanged(DEVICE_STATE_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_FOLDED) val inOrder = Mockito.inOrder(listener) inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) @@ -94,8 +95,8 @@ class FoldStateListenerTest : SysuiTestCase() { @Test fun onStateChanged_stateFoldedThenHalfFolded_notifiesTwice() { - sut.onStateChanged(DEVICE_STATE_FOLDED) - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) val inOrder = Mockito.inOrder(listener) inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) @@ -117,9 +118,18 @@ class FoldStateListenerTest : SysuiTestCase() { } companion object { - private const val DEVICE_STATE_FOLDED = 123 - private const val DEVICE_STATE_HALF_FOLDED = 456 - private const val DEVICE_STATE_UNFOLDED = 789 + private val DEVICE_STATE_FOLDED = DeviceState( + DeviceState.Configuration.Builder(123 /* id */, "FOLDED" /* name */) + .build() + ) + private val DEVICE_STATE_HALF_FOLDED = DeviceState( + DeviceState.Configuration.Builder(456 /* id */, "HALF_FOLDED" /* name */) + .build() + ) + private val DEVICE_STATE_UNFOLDED = DeviceState( + DeviceState.Configuration.Builder(789 /* id */, "UNFOLDED" /* name */) + .build() + ) private const val FOLDED = true private const val NOT_FOLDED = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt index ce471705ed85..c606511456fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy +import android.hardware.devicestate.DeviceState import android.hardware.devicestate.DeviceStateManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST import com.android.systemui.statusbar.policy.DevicePostureController.SUPPORTED_POSTURES_SIZE import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -72,6 +74,18 @@ class DevicePostureControllerImplTest : SysuiTestCase() { com.android.internal.R.array.config_device_state_postures, deviceStateToPostureMapping ) + whenever(deviceStateManager.supportedDeviceStates) + .thenReturn( + listOf( + DEVICE_STATE_CLOSED, + DEVICE_STATE_HALF_FOLDED, + DEVICE_STATE_OPENED, + DEVICE_STATE_FLIPPED, + DEVICE_STATE_UNKNOWN, + DEVICE_STATE_USE_BASE_STATE + ) + ) + underTest = DevicePostureControllerImpl( context, @@ -86,20 +100,20 @@ class DevicePostureControllerImplTest : SysuiTestCase() { var posture = -1 underTest.addCallback { posture = it } - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_UNKNOWN) - assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN) - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_CLOSED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_CLOSED) assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_OPENED) assertThat(posture).isEqualTo(DEVICE_POSTURE_OPENED) - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_FLIPPED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_FLIPPED) assertThat(posture).isEqualTo(DEVICE_POSTURE_FLIPPED) + + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_UNKNOWN) + assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN) } @Test @@ -107,15 +121,26 @@ class DevicePostureControllerImplTest : SysuiTestCase() { var posture = -1 underTest.addCallback { posture = it } - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - // base state change doesn't change the posture - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) + val physicalProperties = + setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED) + val updatedState = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_STATE_HALF_FOLDED.identifier, + DEVICE_STATE_HALF_FOLDED.name + ) + .setPhysicalProperties(physicalProperties) + .build() + ) + // state change with updated physical properties shouldn't cause a posture change + deviceStateCallback.value.onDeviceStateChanged(updatedState) assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - // WHEN the display state maps to using the base state, then posture updates - deviceStateCallback.value.onStateChanged(useBaseStateDeviceState) + // WHEN the display state maps to the physical state, then posture updates + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_USE_BASE_STATE) assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) } @@ -124,20 +149,97 @@ class DevicePostureControllerImplTest : SysuiTestCase() { var numPostureChanges = 0 underTest.addCallback { numPostureChanges++ } - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) assertThat(numPostureChanges).isEqualTo(1) - // base state changes doesn't send another posture update since the device state isn't - // useBaseStateDeviceState - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_HALF_OPENED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_FLIPPED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_OPENED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_UNKNOWN) + // update to physical properties doesn't send another posture update since the device state + // isn't useBaseStateDeviceState + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_CLOSED) + ) + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_HALF_FOLDED) + ) + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_OPENED) + ) + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_UNKNOWN) + ) assertThat(numPostureChanges).isEqualTo(1) } private fun verifyRegistersForDeviceStateCallback() { verify(deviceStateManager).registerCallback(eq(mainExecutor), deviceStateCallback.capture()) } + + private fun getStateUpdatedPhysicalProperties( + currentState: DeviceState, + physicalState: DeviceState + ): DeviceState { + return DeviceState( + DeviceState.Configuration.Builder(currentState.identifier, currentState.name) + .setSystemProperties(currentState.configuration.systemProperties) + .setPhysicalProperties(physicalState.configuration.physicalProperties) + .build() + ) + } + + companion object { + val DEVICE_STATE_CLOSED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_CLOSED /* id */, + "CLOSED" /* name */ + ) + .setPhysicalProperties( + setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED) + ) + .build() + ) + val DEVICE_STATE_HALF_FOLDED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_HALF_OPENED /* id */, + "HALF_FOLDED" /* name */ + ) + .setPhysicalProperties( + setOf( + DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN + ) + ) + .build() + ) + val DEVICE_STATE_OPENED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_OPENED /* id */, + "OPENED" /* name */ + ) + .setPhysicalProperties( + setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN) + ) + .build() + ) + val DEVICE_STATE_FLIPPED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_FLIPPED /* id */, + "FLIPPED" /* name */ + ) + .build() + ) + val DEVICE_STATE_UNKNOWN = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_UNKNOWN /* id */, + "UNKNOWN" /* name */ + ) + .build() + ) + val DEVICE_STATE_USE_BASE_STATE = + DeviceState( + DeviceState.Configuration.Builder(SUPPORTED_POSTURES_SIZE, "USE_BASE_STATE").build() + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index 4ccbd1b739f3..2955162f80c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.os.UserHandle; import android.provider.Settings; @@ -119,11 +120,11 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); // Settings only exist for state 0 and 1 - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); } @@ -134,10 +135,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); } @@ -147,7 +148,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mFakeRotationPolicy.setRotationLock(true); // State 2 -> Ignored -> Fall back to state 1 which is unlocked - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); } @@ -161,7 +162,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mFakeRotationPolicy.setRotationLock(false); // State 2 -> Ignored -> Fall back to state 1 which is locked - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); } @@ -173,7 +174,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mSettingsManager.onPersistedSettingsChanged(); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( @@ -189,10 +190,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase @Test public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() { - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); } @@ -202,10 +203,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateCallback.onStateChanged(8); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( @@ -225,7 +226,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(false); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); @@ -241,7 +242,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase initializeSettingsWith( 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( /* rotationLocked= */ false, @@ -262,7 +263,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( /* rotationLocked= */ true, @@ -283,8 +284,8 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 8, DEVICE_STATE_ROTATION_LOCK_IGNORED); - mDeviceStateCallback.onStateChanged(1); - mDeviceStateCallback.onStateChanged(8); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8)); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( /* rotationLocked= */ true, @@ -320,6 +321,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mSettingsManager.onPersistedSettingsChanged(); } + private DeviceState createDeviceStateForIdentifier(int id) { + return new DeviceState(new DeviceState.Configuration.Builder(id, "" /* name */).build()); + } + private static class FakeRotationPolicy implements RotationPolicyWrapper { private boolean mRotationLock; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt index 5c5016daf029..e2b5869fce99 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt @@ -16,9 +16,24 @@ package com.android.systemui.jank +import android.os.HandlerThread import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.jank.InteractionJankMonitor.Configuration.Builder import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.spy -val Kosmos.interactionJankMonitor by Fixture<InteractionJankMonitor> { mock() } +val Kosmos.interactionJankMonitor by + Fixture<InteractionJankMonitor> { + spy(InteractionJankMonitor(HandlerThread("InteractionJankMonitor-Kosmos"))).apply { + doReturn(true).`when`(this).shouldMonitor() + doReturn(true).`when`(this).begin(any(), anyInt()) + doReturn(true).`when`(this).begin(any<Builder>()) + doReturn(true).`when`(this).end(anyInt()) + doReturn(true).`when`(this).cancel(anyInt()) + doReturn(true).`when`(this).cancel(anyInt(), anyInt()) + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java index 63f7c9755782..ea59c0a24cf8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.content.pm.ActivityInfo; import android.graphics.Canvas; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java index 478658eb232d..3b7b158264cf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static android.util.MathUtils.constrain; @@ -32,6 +32,8 @@ import android.hardware.HardwareBuffer; import android.media.Image; import android.util.Log; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient; + import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 177c345686b9..e2ae3def0b1b 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -76,7 +76,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -397,11 +396,10 @@ public final class DeviceStateManagerService extends SystemService { @NonNull private DeviceStateInfo getDeviceStateInfoLocked() { final List<DeviceState> supportedStates = getSupportedStatesLocked(); - final DeviceState baseState = mBaseState.orElse(null); - final DeviceState currentState = mCommittedState.orElse(null); + final DeviceState baseState = mBaseState.orElse(INVALID_DEVICE_STATE); + final DeviceState currentState = mCommittedState.orElse(INVALID_DEVICE_STATE); - return new DeviceStateInfo(supportedStates, - baseState != null ? baseState : INVALID_DEVICE_STATE, + return new DeviceStateInfo(supportedStates, baseState, createMergedDeviceState(currentState, baseState)); } @@ -412,7 +410,7 @@ public final class DeviceStateManagerService extends SystemService { */ private DeviceState createMergedDeviceState(@Nullable DeviceState committedState, @Nullable DeviceState baseState) { - if (committedState == null) { + if (committedState.equals(INVALID_DEVICE_STATE)) { return INVALID_DEVICE_STATE; } @@ -420,8 +418,7 @@ public final class DeviceStateManagerService extends SystemService { committedState.getConfiguration().getSystemProperties(); Set<@DeviceState.DeviceStateProperties Integer> physicalProperties = - baseState != null ? baseState.getConfiguration().getPhysicalProperties() - : Collections.emptySet(); + baseState.getConfiguration().getPhysicalProperties(); DeviceState.Configuration deviceStateConfiguration = new DeviceState.Configuration.Builder( committedState.getIdentifier(), committedState.getName()) diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 9b2dcc53f456..411666942b6d 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -57,6 +57,9 @@ import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HighBrightnessMode; +import com.android.server.display.config.IdleScreenRefreshRateTimeout; +import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; +import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds; import com.android.server.display.config.IntegerArray; import com.android.server.display.config.LuxThrottling; import com.android.server.display.config.NitsMap; @@ -553,6 +556,18 @@ import javax.xml.datatype.DatatypeConfigurationException; * <minorVersion>0</minorVersion> * </usiVersion> * <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode> + * <idleScreenRefreshRateTimeout> + * <luxThresholds> + * <point> + * <lux>6</lux> + * <timeout>1000</timeout> + * </point> + * <point> + * <lux>10</lux> + * <timeout>800</timeout> + * </point> + * </luxThresholds> + * </idleScreenRefreshRateTimeout> * </displayConfiguration> * } * </pre> @@ -843,6 +858,14 @@ public class DisplayDeviceConfig { private final Map<BrightnessLimitMapType, Map<Float, Float>> mLuxThrottlingData = new HashMap<>(); + /** + * The idle screen timeout configuration for switching to lower refresh rate + */ + @NonNull + private List<IdleScreenRefreshRateTimeoutLuxThresholdPoint> + mIdleScreenRefreshRateTimeoutLuxThresholds = new ArrayList<>(); + + @Nullable private HostUsiVersion mHostUsiVersion; @@ -1999,6 +2022,7 @@ public class DisplayDeviceConfig { loadUsiVersion(config); mHdrBrightnessData = HdrBrightnessData.loadConfig(config); loadBrightnessCapForWearBedtimeMode(config); + loadIdleScreenRefreshRateTimeoutConfigs(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } @@ -2024,6 +2048,7 @@ public class DisplayDeviceConfig { loadAutoBrightnessAvailableFromConfigXml(); loadRefreshRateSetting(null); loadBrightnessCapForWearBedtimeModeFromConfigXml(); + loadIdleScreenRefreshRateTimeoutConfigs(null); mLoadedFrom = "<config.xml>"; } @@ -3326,6 +3351,47 @@ public class DisplayDeviceConfig { } } + private void loadIdleScreenRefreshRateTimeoutConfigs(@Nullable DisplayConfiguration config) { + if (mFlags.isIdleScreenRefreshRateTimeoutEnabled() + && config != null && config.getIdleScreenRefreshRateTimeout() != null) { + validateIdleScreenRefreshRateTimeoutConfig( + config.getIdleScreenRefreshRateTimeout()); + mIdleScreenRefreshRateTimeoutLuxThresholds = config + .getIdleScreenRefreshRateTimeout().getLuxThresholds().getPoint(); + } + } + + private void validateIdleScreenRefreshRateTimeoutConfig( + IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) { + IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds = + idleScreenRefreshRateTimeoutConfig.getLuxThresholds(); + + if (idleScreenRefreshRateTimeoutLuxThresholds != null) { + int previousLux = -1; + // Validate that the lux values are in the increasing order + for (IdleScreenRefreshRateTimeoutLuxThresholdPoint point : + idleScreenRefreshRateTimeoutLuxThresholds.getPoint()) { + int newLux = point.getLux().intValue(); + if (previousLux >= newLux) { + throw new RuntimeException("Lux values should be in ascending order in the" + + " idle screen refresh rate timeout config"); + } + previousLux = newLux; + } + } + } + + /** + * Gets the idle screen refresh rate timeout(in ms) configuration list. For each entry, the lux + * value represent the lower bound of the lux range, and the value of the lux in the next + * point(INF if not present) represents the upper bound for the corresponding timeout(in ms) + */ + @NonNull + public List<IdleScreenRefreshRateTimeoutLuxThresholdPoint> + getIdleScreenRefreshRateTimeoutLuxThresholdPoint() { + return mIdleScreenRefreshRateTimeoutLuxThresholds; + } + /** * Extracts a float array from the specified {@link TypedArray}. * diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ce7c22438d54..84eebe838954 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT; import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_DISPLAYS; +import static android.Manifest.permission.RESTRICT_DISPLAY_MODES; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.hardware.display.DisplayManager.EventsMask; @@ -75,6 +76,7 @@ import android.graphics.Point; import android.hardware.OverlayProperties; import android.hardware.Sensor; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManagerInternal; import android.hardware.display.AmbientBrightnessDayStats; @@ -4530,6 +4532,14 @@ public final class DisplayManagerService extends SystemService { disableConnectedDisplay_enforcePermission(); DisplayManagerService.this.enableConnectedDisplay(displayId, false); } + + @EnforcePermission(RESTRICT_DISPLAY_MODES) + @Override // Binder call + public void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) { + requestDisplayModes_enforcePermission(); + DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes( + token, displayId, modeIds); + } } private static boolean isValidBrightness(float brightness) { @@ -5034,30 +5044,22 @@ public final class DisplayManagerService extends SystemService { * Listens to changes in device state and reports the state to LogicalDisplayMapper. */ class DeviceStateListener implements DeviceStateManager.DeviceStateCallback { - // Base state corresponds to the physical state of the device - private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; @Override - public void onStateChanged(int deviceState) { - boolean isDeviceStateOverrideActive = deviceState != mBaseState; + public void onDeviceStateChanged(DeviceState deviceState) { synchronized (mSyncRoot) { // Notify WindowManager that we are about to handle new device state, this should // be sent before any work related to the device state in DisplayManager, so // WindowManager could do implement that depends on the device state and display // changes (serializes device state update and display change events) Message msg = mHandler.obtainMessage(MSG_RECEIVED_DEVICE_STATE); - msg.arg1 = deviceState; + msg.arg1 = deviceState.getIdentifier(); mHandler.sendMessage(msg); mLogicalDisplayMapper - .setDeviceStateLocked(deviceState, isDeviceStateOverrideActive); + .setDeviceStateLocked(deviceState.getIdentifier()); } } - - @Override - public void onBaseStateChanged(int state) { - mBaseState = state; - } }; private class BrightnessPair { diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 21f90d4aeb94..f727eac71be8 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -441,7 +441,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mVirtualDeviceDisplayMapping.put(displayDevice.getUniqueId(), virtualDeviceUniqueId); } - void setDeviceStateLocked(int state, boolean isOverrideActive) { + void setDeviceStateLocked(int state) { if (!mBootCompleted) { // The boot animation might still be in progress, we do not want to switch states now // as the boot animation would end up with an incorrect size. @@ -465,7 +465,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState, - isOverrideActive, mInteractive, mBootCompleted); + mInteractive, mBootCompleted); // If all displays are off already, we can just transition here, unless we are trying to // wake or sleep the device as part of this transition. In that case defer the final @@ -513,8 +513,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mBootCompleted = true; if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) { - setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot, - /* isOverrideActive= */ false); + setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot); } } } @@ -560,7 +559,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { * * @param pendingState device state we are moving to * @param currentState device state we are currently in - * @param isOverrideActive if a device state override is currently active or not * @param isInteractive if the device is in an interactive state * @param isBootCompleted is the device fully booted * @@ -568,13 +566,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { * @see #setDeviceStateLocked */ @VisibleForTesting - boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive, - boolean isInteractive, boolean isBootCompleted) { + boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isInteractive, + boolean isBootCompleted) { return currentState != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER && mDeviceStatesOnWhichToSleep.get(pendingState) && !mDeviceStatesOnWhichToSleep.get(currentState) - && !isOverrideActive - && isInteractive && isBootCompleted + && isInteractive + && isBootCompleted && !mFoldSettingProvider.shouldStayAwakeOnFold(); } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 5f455db39dd4..e1a166ec95f5 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -92,9 +92,9 @@ public class DisplayManagerFlags { Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION, Flags::brightnessIntRangeUserPerception); - private final FlagState mVsyncProximityVote = new FlagState( - Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE, - Flags::enableExternalVsyncProximityVote); + private final FlagState mRestrictDisplayModes = new FlagState( + Flags.FLAG_ENABLE_RESTRICT_DISPLAY_MODES, + Flags::enableRestrictDisplayModes); private final FlagState mVsyncLowPowerVote = new FlagState( Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE, @@ -135,6 +135,11 @@ public class DisplayManagerFlags { Flags::sensorBasedBrightnessThrottling ); + private final FlagState mIdleScreenRefreshRateTimeout = new FlagState( + Flags.FLAG_IDLE_SCREEN_REFRESH_RATE_TIMEOUT, + Flags::idleScreenRefreshRateTimeout + ); + private final FlagState mRefactorDisplayPowerController = new FlagState( Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER, @@ -237,8 +242,8 @@ public class DisplayManagerFlags { return mBrightnessIntRangeUserPerceptionFlagState.isEnabled(); } - public boolean isVsyncProximityVoteEnabled() { - return mVsyncProximityVote.isEnabled(); + public boolean isRestrictDisplayModesEnabled() { + return mRestrictDisplayModes.isEnabled(); } public boolean isVsyncLowPowerVoteEnabled() { @@ -280,6 +285,10 @@ public class DisplayManagerFlags { return mSensorBasedBrightnessThrottling.isEnabled(); } + public boolean isIdleScreenRefreshRateTimeoutEnabled() { + return mIdleScreenRefreshRateTimeout.isEnabled(); + } + public boolean isRefactorDisplayPowerControllerEnabled() { return mRefactorDisplayPowerController.isEnabled(); } @@ -302,7 +311,7 @@ public class DisplayManagerFlags { pw.println(" " + mPowerThrottlingClamperFlagState); pw.println(" " + mSmallAreaDetectionFlagState); pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState); - pw.println(" " + mVsyncProximityVote); + pw.println(" " + mRestrictDisplayModes); pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState); pw.println(" " + mAutoBrightnessModesFlagState); pw.println(" " + mFastHdrTransitions); @@ -310,6 +319,7 @@ public class DisplayManagerFlags { pw.println(" " + mRefreshRateVotingTelemetry); pw.println(" " + mPixelAnisotropyCorrectionEnabled); pw.println(" " + mSensorBasedBrightnessThrottling); + pw.println(" " + mIdleScreenRefreshRateTimeout); pw.println(" " + mRefactorDisplayPowerController); } diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index d2909b898704..a5f241f4d68e 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -130,9 +130,9 @@ flag { } flag { - name: "enable_external_vsync_proximity_vote" + name: "enable_restrict_display_modes" namespace: "display_manager" - description: "Feature flag for external vsync proximity vote" + description: "Feature flag for restriction display modes api" bug: "284866750" is_fixed_read_only: true } @@ -219,3 +219,11 @@ flag { bug: "294444204" is_fixed_read_only: true } + +flag { + name: "idle_screen_refresh_rate_timeout" + namespace: "display_manager" + description: "Feature flag for reducing the refresh rate when the screen is idle after a timeout" + bug: "310026579" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java index c53823139ffe..6d750c0aa3cb 100644 --- a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java +++ b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class BaseModeRefreshRateVote implements Vote { @@ -31,7 +33,7 @@ class BaseModeRefreshRateVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { if (summary.appRequestBaseModeRefreshRate == 0f && mAppRequestBaseModeRefreshRate > 0f) { summary.appRequestBaseModeRefreshRate = mAppRequestBaseModeRefreshRate; diff --git a/services/core/java/com/android/server/display/mode/CombinedVote.java b/services/core/java/com/android/server/display/mode/CombinedVote.java index 4b68791268e9..3cd16bf5c640 100644 --- a/services/core/java/com/android/server/display/mode/CombinedVote.java +++ b/services/core/java/com/android/server/display/mode/CombinedVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Collections; import java.util.List; import java.util.Objects; @@ -28,7 +30,7 @@ class CombinedVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { mVotes.forEach(vote -> vote.updateSummary(summary)); } diff --git a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java index 7f5740690c7f..7abb518ec494 100644 --- a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java +++ b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class DisableRefreshRateSwitchingVote implements Vote { @@ -31,7 +33,7 @@ class DisableRefreshRateSwitchingVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.disableRefreshRateSwitching = summary.disableRefreshRateSwitching || mDisableRefreshRateSwitching; } diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 64cbd5488d90..495ae87fe0b9 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -41,6 +41,7 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.net.Uri; import android.os.Handler; +import android.os.IBinder; import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.Looper; @@ -80,7 +81,6 @@ import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.DeviceConfigParsingUtils; import com.android.server.display.utils.SensorUtils; import com.android.server.sensors.SensorManagerInternal; -import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; @@ -128,9 +128,12 @@ public class DisplayModeDirector { private final SettingsObserver mSettingsObserver; private final DisplayObserver mDisplayObserver; private final UdfpsObserver mUdfpsObserver; - private final SensorObserver mSensorObserver; + private final ProximitySensorObserver mSensorObserver; private final HbmObserver mHbmObserver; private final SkinThermalStatusObserver mSkinThermalStatusObserver; + + @Nullable + private final SystemRequestObserver mSystemRequestObserver; private final DeviceConfigParameterProvider mConfigParameterProvider; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @@ -203,6 +206,7 @@ public class DisplayModeDirector { .isDisplaysRefreshRatesSynchronizationEnabled(); mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled(); + mContext = context; mHandler = new DisplayModeDirectorHandler(handler.getLooper()); mInjector = injector; @@ -222,10 +226,15 @@ public class DisplayModeDirector { mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked, mVotesStatsReporter); mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage); - mSensorObserver = new SensorObserver(context, mVotesStorage, injector); + mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector); mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage); mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); + if (mDvrrSupported && displayManagerFlags.isRestrictDisplayModesEnabled()) { + mSystemRequestObserver = new SystemRequestObserver(mVotesStorage); + } else { + mSystemRequestObserver = null; + } mAlwaysRespectAppRequest = false; mSupportsFrameRateOverride = injector.supportsFrameRateOverride(); } @@ -520,6 +529,15 @@ public class DisplayModeDirector { } /** + * Delegates requestDisplayModes call to SystemRequestObserver + */ + public void requestDisplayModes(IBinder token, int displayId, int[] modeIds) { + if (mSystemRequestObserver != null) { + mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds); + } + } + + /** * Print the object's state and debug information into the given stream. * * @param pw The stream to dump information to. @@ -970,10 +988,10 @@ public class DisplayModeDirector { Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; final Vote vote; if (inLowPowerMode && mVsynLowPowerVoteEnabled) { - vote = Vote.forSupportedModes(List.of( - new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f, + vote = Vote.forSupportedRefreshRates(List.of( + new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, /* vsyncRate= */ 240f), - new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f, + new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f) )); } else if (inLowPowerMode) { @@ -2158,11 +2176,11 @@ public class DisplayModeDirector { } if (mVsyncLowLightBlockingVoteEnabled) { - refreshRateSwitchingVote = Vote.forSupportedModesAndDisableRefreshRateSwitching( + refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching( List.of( - new SupportedModesVote.SupportedMode( + new SupportedRefreshRatesVote.RefreshRates( /* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f), - new SupportedModesVote.SupportedMode( + new SupportedRefreshRatesVote.RefreshRates( /* peakRefreshRate= */120f, /* vsyncRate= */ 120f))); } else { refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); @@ -2498,116 +2516,6 @@ public class DisplayModeDirector { } } - protected static final class SensorObserver implements ProximityActiveListener, - DisplayManager.DisplayListener { - private final String mProximitySensorName = null; - private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; - - private final VotesStorage mVotesStorage; - private final Context mContext; - private final Injector mInjector; - @GuardedBy("mSensorObserverLock") - private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray(); - private final Object mSensorObserverLock = new Object(); - - private DisplayManager mDisplayManager; - private DisplayManagerInternal mDisplayManagerInternal; - @GuardedBy("mSensorObserverLock") - private boolean mIsProxActive = false; - - SensorObserver(Context context, VotesStorage votesStorage, Injector injector) { - mContext = context; - mVotesStorage = votesStorage; - mInjector = injector; - } - - @Override - public void onProximityActive(boolean isActive) { - synchronized (mSensorObserverLock) { - if (mIsProxActive != isActive) { - mIsProxActive = isActive; - recalculateVotesLocked(); - } - } - } - - public void observe() { - mDisplayManager = mContext.getSystemService(DisplayManager.class); - mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); - - final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal(); - sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); - - synchronized (mSensorObserverLock) { - for (Display d : mInjector.getDisplays()) { - mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); - } - } - mInjector.registerDisplayListener(this, BackgroundThread.getHandler(), - DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); - } - - private void recalculateVotesLocked() { - final Display[] displays = mInjector.getDisplays(); - for (Display d : displays) { - int displayId = d.getDisplayId(); - Vote vote = null; - if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) { - final RefreshRateRange rate = - mDisplayManagerInternal.getRefreshRateForDisplayAndSensor( - displayId, mProximitySensorName, mProximitySensorType); - if (rate != null) { - vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); - } - } - mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); - } - } - - void dump(PrintWriter pw) { - pw.println(" SensorObserver"); - synchronized (mSensorObserverLock) { - pw.println(" mIsProxActive=" + mIsProxActive); - pw.println(" mDozeStateByDisplay:"); - for (int i = 0; i < mDozeStateByDisplay.size(); i++) { - final int id = mDozeStateByDisplay.keyAt(i); - final boolean dozed = mDozeStateByDisplay.valueAt(i); - pw.println(" " + id + " -> " + dozed); - } - } - } - - @Override - public void onDisplayAdded(int displayId) { - boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.put(displayId, isDozeState); - recalculateVotesLocked(); - } - } - - @Override - public void onDisplayChanged(int displayId) { - boolean wasDozeState = mDozeStateByDisplay.get(displayId); - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.put(displayId, - mInjector.isDozeState(mInjector.getDisplay(displayId))); - if (wasDozeState != mDozeStateByDisplay.get(displayId)) { - recalculateVotesLocked(); - } - } - } - - @Override - public void onDisplayRemoved(int displayId) { - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.delete(displayId); - recalculateVotesLocked(); - } - } - } /** * Listens to DisplayManager for HBM status and applies any refresh-rate restrictions for diff --git a/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java new file mode 100644 index 000000000000..11418c147caa --- /dev/null +++ b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode; + +import android.hardware.Sensor; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.util.SparseBooleanArray; +import android.view.Display; +import android.view.SurfaceControl; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; +import com.android.server.sensors.SensorManagerInternal; + +import java.io.PrintWriter; + +class ProximitySensorObserver implements + SensorManagerInternal.ProximityActiveListener, + DisplayManager.DisplayListener { + private final String mProximitySensorName = null; + private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; + + private final VotesStorage mVotesStorage; + private final DisplayModeDirector.Injector mInjector; + @GuardedBy("mSensorObserverLock") + private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray(); + private final Object mSensorObserverLock = new Object(); + private DisplayManagerInternal mDisplayManagerInternal; + @GuardedBy("mSensorObserverLock") + private boolean mIsProxActive = false; + + ProximitySensorObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector) { + mVotesStorage = votesStorage; + mInjector = injector; + } + + @Override + public void onProximityActive(boolean isActive) { + synchronized (mSensorObserverLock) { + if (mIsProxActive != isActive) { + mIsProxActive = isActive; + recalculateVotesLocked(); + } + } + } + + void observe() { + mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); + + final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal(); + sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); + + synchronized (mSensorObserverLock) { + for (Display d : mInjector.getDisplays()) { + mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); + } + } + mInjector.registerDisplayListener(this, BackgroundThread.getHandler(), + DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + } + + @GuardedBy("mSensorObserverLock") + private void recalculateVotesLocked() { + final Display[] displays = mInjector.getDisplays(); + for (Display d : displays) { + int displayId = d.getDisplayId(); + Vote vote = null; + if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) { + final SurfaceControl.RefreshRateRange rate = + mDisplayManagerInternal.getRefreshRateForDisplayAndSensor( + displayId, mProximitySensorName, mProximitySensorType); + if (rate != null) { + vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); + } + } + mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); + } + } + + void dump(PrintWriter pw) { + pw.println(" SensorObserver"); + synchronized (mSensorObserverLock) { + pw.println(" mIsProxActive=" + mIsProxActive); + pw.println(" mDozeStateByDisplay:"); + for (int i = 0; i < mDozeStateByDisplay.size(); i++) { + final int id = mDozeStateByDisplay.keyAt(i); + final boolean dozed = mDozeStateByDisplay.valueAt(i); + pw.println(" " + id + " -> " + dozed); + } + } + } + + @Override + public void onDisplayAdded(int displayId) { + boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); + synchronized (mSensorObserverLock) { + mDozeStateByDisplay.put(displayId, isDozeState); + recalculateVotesLocked(); + } + } + + @Override + public void onDisplayChanged(int displayId) { + synchronized (mSensorObserverLock) { + boolean wasDozeState = mDozeStateByDisplay.get(displayId); + mDozeStateByDisplay.put(displayId, + mInjector.isDozeState(mInjector.getDisplay(displayId))); + if (wasDozeState != mDozeStateByDisplay.get(displayId)) { + recalculateVotesLocked(); + } + } + } + + @Override + public void onDisplayRemoved(int displayId) { + synchronized (mSensorObserverLock) { + mDozeStateByDisplay.delete(displayId); + recalculateVotesLocked(); + } + } +} diff --git a/services/core/java/com/android/server/display/mode/RefreshRateVote.java b/services/core/java/com/android/server/display/mode/RefreshRateVote.java index 670b8a13da4d..b96ab3b6be3d 100644 --- a/services/core/java/com/android/server/display/mode/RefreshRateVote.java +++ b/services/core/java/com/android/server/display/mode/RefreshRateVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; @@ -64,7 +66,7 @@ abstract class RefreshRateVote implements Vote { * Vote: min(ignored) min(applied) min(applied+physical) max(applied) max(ignored) */ @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, mMinRefreshRate); summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate); // Physical refresh rate cannot be lower than the minimal render frame rate. @@ -97,7 +99,7 @@ abstract class RefreshRateVote implements Vote { * Vote: min(ignored) min(applied) max(applied+render) max(applied) max(ignored) */ @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate, mMinRefreshRate); summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate, diff --git a/services/core/java/com/android/server/display/mode/SizeVote.java b/services/core/java/com/android/server/display/mode/SizeVote.java index f2f8dc451098..f5a5abea9d9e 100644 --- a/services/core/java/com/android/server/display/mode/SizeVote.java +++ b/services/core/java/com/android/server/display/mode/SizeVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class SizeVote implements Vote { @@ -48,7 +50,7 @@ class SizeVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { if (mHeight > 0 && mWidth > 0) { // For display size, disable refresh rate switching and base mode refresh rate use // only the first vote we come across (i.e. the highest priority vote that includes diff --git a/services/core/java/com/android/server/display/mode/SupportedModesVote.java b/services/core/java/com/android/server/display/mode/SupportedModesVote.java index 7eebcc050b16..0cf8311128d0 100644 --- a/services/core/java/com/android/server/display/mode/SupportedModesVote.java +++ b/services/core/java/com/android/server/display/mode/SupportedModesVote.java @@ -16,77 +16,42 @@ package com.android.server.display.mode; -import java.util.ArrayList; +import android.annotation.NonNull; + import java.util.Collections; import java.util.List; import java.util.Objects; -class SupportedModesVote implements Vote { +public class SupportedModesVote implements Vote { - final List<SupportedMode> mSupportedModes; + final List<Integer> mModeIds; - SupportedModesVote(List<SupportedMode> supportedModes) { - mSupportedModes = Collections.unmodifiableList(supportedModes); + SupportedModesVote(List<Integer> modeIds) { + mModeIds = Collections.unmodifiableList(modeIds); } - - /** - * Summary should have subset of supported modes. - * If Vote1.supportedModes=(A,B), Vote2.supportedModes=(B,C) then summary.supportedModes=(B) - * If summary.supportedModes==null then there is no restriction on supportedModes - */ @Override - public void updateSummary(VoteSummary summary) { - if (summary.supportedModes == null) { - summary.supportedModes = new ArrayList<>(mSupportedModes); + public void updateSummary(@NonNull VoteSummary summary) { + if (summary.supportedModeIds == null) { + summary.supportedModeIds = mModeIds; } else { - summary.supportedModes.retainAll(mSupportedModes); + summary.supportedModeIds.retainAll(mModeIds); } } @Override + public String toString() { + return "SupportedModesVote{ mModeIds=" + mModeIds + " }"; + } + + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SupportedModesVote that)) return false; - return mSupportedModes.equals(that.mSupportedModes); + return mModeIds.equals(that.mModeIds); } @Override public int hashCode() { - return Objects.hash(mSupportedModes); - } - - @Override - public String toString() { - return "SupportedModesVote{ mSupportedModes=" + mSupportedModes + " }"; - } - - static class SupportedMode { - final float mPeakRefreshRate; - final float mVsyncRate; - - - SupportedMode(float peakRefreshRate, float vsyncRate) { - mPeakRefreshRate = peakRefreshRate; - mVsyncRate = vsyncRate; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SupportedMode that)) return false; - return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0 - && Float.compare(that.mVsyncRate, mVsyncRate) == 0; - } - - @Override - public int hashCode() { - return Objects.hash(mPeakRefreshRate, mVsyncRate); - } - - @Override - public String toString() { - return "SupportedMode{ mPeakRefreshRate=" + mPeakRefreshRate - + ", mVsyncRate=" + mVsyncRate + " }"; - } + return Objects.hash(mModeIds); } } diff --git a/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java new file mode 100644 index 000000000000..5305487b2ddd --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode; + +import android.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +class SupportedRefreshRatesVote implements Vote { + + final List<RefreshRates> mRefreshRates; + + SupportedRefreshRatesVote(List<RefreshRates> refreshRates) { + mRefreshRates = Collections.unmodifiableList(refreshRates); + } + + /** + * Summary should have subset of supported modes. + * If Vote1.refreshRates=(A,B), Vote2.refreshRates=(B,C) + * then summary.supportedRefreshRates=(B) + * If summary.supportedRefreshRates==null then there is no restriction on supportedRefreshRates + */ + @Override + public void updateSummary(@NonNull VoteSummary summary) { + if (summary.supportedRefreshRates == null) { + summary.supportedRefreshRates = new ArrayList<>(mRefreshRates); + } else { + summary.supportedRefreshRates.retainAll(mRefreshRates); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SupportedRefreshRatesVote that)) return false; + return mRefreshRates.equals(that.mRefreshRates); + } + + @Override + public int hashCode() { + return Objects.hash(mRefreshRates); + } + + @Override + public String toString() { + return "SupportedRefreshRatesVote{ mSupportedModes=" + mRefreshRates + " }"; + } + + static class RefreshRates { + final float mPeakRefreshRate; + final float mVsyncRate; + + RefreshRates(float peakRefreshRate, float vsyncRate) { + mPeakRefreshRate = peakRefreshRate; + mVsyncRate = vsyncRate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RefreshRates that)) return false; + return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0 + && Float.compare(that.mVsyncRate, mVsyncRate) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mPeakRefreshRate, mVsyncRate); + } + + @Override + public String toString() { + return "RefreshRates{ mPeakRefreshRate=" + mPeakRefreshRate + + ", mVsyncRate=" + mVsyncRate + " }"; + } + } +} diff --git a/services/core/java/com/android/server/display/mode/SystemRequestObserver.java b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java new file mode 100644 index 000000000000..15f19cca99db --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * SystemRequestObserver responsible for handling system requests to filter allowable display + * modes + */ +class SystemRequestObserver { + private final VotesStorage mVotesStorage; + + private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + // noop, binderDied(@NonNull IBinder who) is overridden + } + @Override + public void binderDied(@NonNull IBinder who) { + removeSystemRequestedVotes(who); + who.unlinkToDeath(mDeathRecipient, 0); + } + }; + + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final Map<IBinder, SparseArray<List<Integer>>> mDisplaysRestrictions = new HashMap<>(); + + SystemRequestObserver(VotesStorage storage) { + mVotesStorage = storage; + } + + void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) { + if (modeIds == null) { + removeSystemRequestedVote(token, displayId); + } else { + addSystemRequestedVote(token, displayId, modeIds); + } + } + + private void addSystemRequestedVote(IBinder token, int displayId, @NonNull int[] modeIds) { + try { + boolean needLinkToDeath = false; + List<Integer> modeIdsList = new ArrayList<>(); + for (int mode: modeIds) { + modeIdsList.add(mode); + } + synchronized (mLock) { + SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token); + if (modesByDisplay == null) { + needLinkToDeath = true; + modesByDisplay = new SparseArray<>(); + mDisplaysRestrictions.put(token, modesByDisplay); + } + + modesByDisplay.put(displayId, modeIdsList); + updateStorageLocked(displayId); + } + if (needLinkToDeath) { + token.linkToDeath(mDeathRecipient, 0); + } + } catch (RemoteException re) { + removeSystemRequestedVotes(token); + } + } + + private void removeSystemRequestedVote(IBinder token, int displayId) { + boolean needToUnlink = false; + synchronized (mLock) { + SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token); + if (modesByDisplay != null) { + modesByDisplay.remove(displayId); + needToUnlink = modesByDisplay.size() == 0; + updateStorageLocked(displayId); + } + } + if (needToUnlink) { + token.unlinkToDeath(mDeathRecipient, 0); + } + } + + private void removeSystemRequestedVotes(IBinder token) { + synchronized (mLock) { + SparseArray<List<Integer>> removed = mDisplaysRestrictions.remove(token); + if (removed != null) { + for (int i = 0; i < removed.size(); i++) { + updateStorageLocked(removed.keyAt(i)); + } + } + } + } + + @GuardedBy("mLock") + private void updateStorageLocked(int displayId) { + List<Integer> modeIds = new ArrayList<>(); + boolean[] modesFound = new boolean[1]; + + mDisplaysRestrictions.forEach((key, value) -> { + List<Integer> modesForDisplay = value.get(displayId); + if (modesForDisplay != null) { + if (!modesFound[0]) { + modeIds.addAll(modesForDisplay); + modesFound[0] = true; + } else { + modeIds.retainAll(modesForDisplay); + } + } + }); + + mVotesStorage.updateVote(displayId, Vote.PRIORITY_SYSTEM_REQUESTED_MODES, + modesFound[0] ? Vote.forSupportedModes(modeIds) : null); + } +} diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index e8d5a194f8f4..5b987f491a45 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.List; interface Vote { @@ -91,26 +93,29 @@ interface Vote { // For concurrent displays we want to limit refresh rate on all displays int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12; + // For internal application to limit display modes to specific ids + int PRIORITY_SYSTEM_REQUESTED_MODES = 13; + // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE = 13; + int PRIORITY_LOW_POWER_MODE = 14; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14; + int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 15; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - int PRIORITY_SKIN_TEMPERATURE = 15; + int PRIORITY_SKIN_TEMPERATURE = 16; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - int PRIORITY_PROXIMITY = 16; + int PRIORITY_PROXIMITY = 17; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - int PRIORITY_UDFPS = 17; + int PRIORITY_UDFPS = 18; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. @@ -128,7 +133,7 @@ interface Vote { */ int INVALID_SIZE = -1; - void updateSummary(VoteSummary summary); + void updateSummary(@NonNull VoteSummary summary); static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) { return new CombinedVote( @@ -166,15 +171,22 @@ interface Vote { return new BaseModeRefreshRateVote(baseModeRefreshRate); } - static Vote forSupportedModes(List<SupportedModesVote.SupportedMode> supportedModes) { - return new SupportedModesVote(supportedModes); + static Vote forSupportedRefreshRates( + List<SupportedRefreshRatesVote.RefreshRates> refreshRates) { + return new SupportedRefreshRatesVote(refreshRates); } + static Vote forSupportedModes(List<Integer> modeIds) { + return new SupportedModesVote(modeIds); + } + + - static Vote forSupportedModesAndDisableRefreshRateSwitching( - List<SupportedModesVote.SupportedMode> supportedModes) { + static Vote forSupportedRefreshRatesAndDisableSwitching( + List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) { return new CombinedVote( - List.of(forDisableRefreshRateSwitching(), forSupportedModes(supportedModes))); + List.of(forDisableRefreshRateSwitching(), + forSupportedRefreshRates(supportedRefreshRates))); } static String priorityToString(int priority) { diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java index 5fc36b589d38..d4ce892eeba9 100644 --- a/services/core/java/com/android/server/display/mode/VoteSummary.java +++ b/services/core/java/com/android/server/display/mode/VoteSummary.java @@ -16,6 +16,7 @@ package com.android.server.display.mode; +import android.annotation.Nullable; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -39,7 +40,11 @@ final class VoteSummary { public boolean disableRefreshRateSwitching; public float appRequestBaseModeRefreshRate; - public List<SupportedModesVote.SupportedMode> supportedModes; + @Nullable + public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates; + + @Nullable + public List<Integer> supportedModeIds; final boolean mIsDisplayResolutionRangeVotingEnabled; @@ -112,6 +117,9 @@ final class VoteSummary { boolean missingBaseModeRefreshRate = appRequestBaseModeRefreshRate > 0f; for (Display.Mode mode : modes) { + if (!validateRefreshRatesSupported(mode)) { + continue; + } if (!validateModeSupported(mode)) { continue; } @@ -253,21 +261,37 @@ final class VoteSummary { } private boolean validateModeSupported(Display.Mode mode) { - if (supportedModes == null || !mSupportedModesVoteEnabled) { + if (supportedModeIds == null || !mSupportedModesVoteEnabled) { + return true; + } + if (supportedModeIds.contains(mode.getModeId())) { return true; } - for (SupportedModesVote.SupportedMode supportedMode : supportedModes) { - if (equalsWithinFloatTolerance(mode.getRefreshRate(), supportedMode.mPeakRefreshRate) - && equalsWithinFloatTolerance(mode.getVsyncRate(), supportedMode.mVsyncRate)) { + if (mLoggingEnabled) { + Slog.w(TAG, "Discarding mode " + mode.getModeId() + + ", supportedMode not found" + + ": mode.modeId=" + mode.getModeId() + + ", supportedModeIds=" + supportedModeIds); + } + return false; + } + + private boolean validateRefreshRatesSupported(Display.Mode mode) { + if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) { + return true; + } + for (SupportedRefreshRatesVote.RefreshRates refreshRates : this.supportedRefreshRates) { + if (equalsWithinFloatTolerance(mode.getRefreshRate(), refreshRates.mPeakRefreshRate) + && equalsWithinFloatTolerance(mode.getVsyncRate(), refreshRates.mVsyncRate)) { return true; } } if (mLoggingEnabled) { Slog.w(TAG, "Discarding mode " + mode.getModeId() - + ", supportedMode not found" + + ", supportedRefreshRates not found" + ": mode.refreshRate=" + mode.getRefreshRate() + ", mode.vsyncRate=" + mode.getVsyncRate() - + ", supportedModes=" + supportedModes); + + ", supportedRefreshRates=" + supportedRefreshRates); } return false; } @@ -298,7 +322,8 @@ final class VoteSummary { return false; } - if (supportedModes != null && mSupportedModesVoteEnabled && supportedModes.isEmpty()) { + if (supportedRefreshRates != null && mSupportedModesVoteEnabled + && supportedRefreshRates.isEmpty()) { if (mLoggingEnabled) { Slog.w(TAG, "Vote summary resulted in empty set (empty supportedModes)"); } @@ -345,7 +370,8 @@ final class VoteSummary { minHeight = 0; disableRefreshRateSwitching = false; appRequestBaseModeRefreshRate = 0f; - supportedModes = null; + supportedRefreshRates = null; + supportedModeIds = null; if (mLoggingEnabled) { Slog.i(TAG, "Summary reset: " + this); } @@ -367,7 +393,8 @@ final class VoteSummary { + ", minHeight=" + minHeight + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate - + ", supportedModes=" + supportedModes + + ", supportedRefreshRates=" + supportedRefreshRates + + ", supportedModeIds=" + supportedModeIds + ", mIsDisplayResolutionRangeVotingEnabled=" + mIsDisplayResolutionRangeVotingEnabled + ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java index e80b9451dd14..7562a525b5f6 100644 --- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java +++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java @@ -117,11 +117,11 @@ class VotesStatsReporter { maxRefreshRate = (int) physicalVote.mMaxRefreshRate; } else if (!ignoreRenderRate && (vote instanceof RefreshRateVote.RenderVote renderVote)) { maxRefreshRate = (int) renderVote.mMaxRefreshRate; - } else if (vote instanceof SupportedModesVote supportedModesVote) { - // SupportedModesVote limits mode by specific refreshRates, so highest rr is allowed + } else if (vote instanceof SupportedRefreshRatesVote refreshRatesVote) { + // SupportedRefreshRatesVote limits mode by refreshRates, so highest rr is allowed maxRefreshRate = 0; - for (SupportedModesVote.SupportedMode mode : supportedModesVote.mSupportedModes) { - maxRefreshRate = Math.max(maxRefreshRate, (int) mode.mPeakRefreshRate); + for (SupportedRefreshRatesVote.RefreshRates rr : refreshRatesVote.mRefreshRates) { + maxRefreshRate = Math.max(maxRefreshRate, (int) rr.mPeakRefreshRate); } } else if (vote instanceof CombinedVote combinedVote) { for (Vote subVote: combinedVote.mVotes) { diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java index 56c7c18c0a11..6becf1c46d05 100644 --- a/services/core/java/com/android/server/display/mode/VotesStorage.java +++ b/services/core/java/com/android/server/display/mode/VotesStorage.java @@ -18,6 +18,7 @@ package com.android.server.display.mode; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -124,6 +125,44 @@ class VotesStorage { } } + /** removes all votes with certain priority from vote storage */ + void removeAllVotesForPriority(int priority) { + if (mLoggingEnabled) { + Slog.i(TAG, "removeAllVotesForPriority(priority=" + + Vote.priorityToString(priority) + ")"); + } + if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) { + Slog.w(TAG, "Received an invalid priority, ignoring:" + + " priority=" + Vote.priorityToString(priority)); + return; + } + IntArray removedVotesDisplayIds = new IntArray(); + synchronized (mStorageLock) { + int size = mVotesByDisplay.size(); + for (int i = 0; i < size; i++) { + SparseArray<Vote> votes = mVotesByDisplay.valueAt(i); + if (votes.get(priority) != null) { + votes.remove(priority); + removedVotesDisplayIds.add(mVotesByDisplay.keyAt(i)); + } + } + } + if (mLoggingEnabled) { + Slog.i(TAG, "Removed votes with priority=" + priority + + " for displays=" + removedVotesDisplayIds); + } + int removedVotesSize = removedVotesDisplayIds.size(); + if (removedVotesSize > 0) { + if (mVotesStatsReporter != null) { + for (int i = 0; i < removedVotesSize; i++) { + mVotesStatsReporter.reportVoteChanged( + removedVotesDisplayIds.get(i), priority, null); + } + } + mListener.onChanged(); + } + } + /** dump class values, for debugging */ void dump(@NonNull PrintWriter pw) { SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>(); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index b50e2bf317b5..6ff8cf3cc2c8 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -56,6 +56,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.SensorManager; import android.hardware.SystemSensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.DisplayManagerInternal; @@ -7152,9 +7153,10 @@ public final class PowerManagerService extends SystemService private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; @Override - public void onStateChanged(int deviceState) { - if (mDeviceState != deviceState) { - mDeviceState = deviceState; + public void onDeviceStateChanged(@NonNull DeviceState deviceState) { + int stateIdentifier = deviceState.getIdentifier(); + if (mDeviceState != stateIdentifier) { + mDeviceState = stateIdentifier; // Device-state interactions are applied to the default display so that they // are reflected only with the default power group. userActivityInternal(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(), diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6fa6957f2949..04e298810dad 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -328,6 +328,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.service.contentcapture.ActivityEvent; @@ -1003,6 +1004,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Whether the Activity allows state sharing in untrusted embedding private final boolean mAllowUntrustedEmbeddingStateSharing; + // TODO(b/329378309): Remove this once the overview handles the configuration correctly. + private static final boolean OVERRIDE_OVERVIEW_CONFIGURATION = + SystemProperties.getBoolean("persist.wm.debug.override_overview_configuration", true); + // Records whether client has overridden the WindowAnimation_(Open/Close)(Enter/Exit)Animation. private CustomAppTransition mCustomOpenTransition; private CustomAppTransition mCustomCloseTransition; @@ -8608,7 +8613,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) { rotation = mDisplayContent.getRotation(); } - if (!mWmService.mFlags.mInsetsDecoupledConfiguration + final int activityType = inOutConfig.windowConfiguration.getActivityType(); + if (OVERRIDE_OVERVIEW_CONFIGURATION + && (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS)) { + // Do not early return and provide the override. This should be removed shortly as we + // don't override 1P components. + } else if (!mWmService.mFlags.mInsetsDecoupledConfiguration || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) || getCompatDisplayInsets() != null || isFloating(parentWindowingMode) || fullBounds == null diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 46bac161f0a6..2b337aed5b87 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5700,7 +5700,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // window becomes visible while the sync group is still active. return true; } - if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn()) { + if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn() + && mPrepareSyncSeqId <= 0) { // Complete the sync state immediately for a drawn window that doesn't need to redraw. onSyncFinishedDrawing(); } diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp index 0a9ce2fed7fc..9dc70afad9d9 100644 --- a/services/core/jni/com_android_server_UsbDeviceManager.cpp +++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp @@ -108,18 +108,6 @@ static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv* /* env return (result == 1); } -static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv* /* env */, jobject /* thiz */) -{ - int fd = open(DRIVER_NAME, O_RDWR); - if (fd < 0) { - ALOGE("could not open %s", DRIVER_NAME); - return false; - } - int result = ioctl(fd, ACCESSORY_GET_AUDIO_MODE); - close(fd); - return result; -} - static jobject android_server_UsbDeviceManager_openControl(JNIEnv *env, jobject /* thiz */, jstring jFunction) { ScopedUtfChars function(env, jFunction); bool ptp = false; @@ -148,16 +136,13 @@ static jobject android_server_UsbDeviceManager_openControl(JNIEnv *env, jobject } static const JNINativeMethod method_table[] = { - { "nativeGetAccessoryStrings", "()[Ljava/lang/String;", - (void*)android_server_UsbDeviceManager_getAccessoryStrings }, - { "nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;", - (void*)android_server_UsbDeviceManager_openAccessory }, - { "nativeIsStartRequested", "()Z", - (void*)android_server_UsbDeviceManager_isStartRequested }, - { "nativeGetAudioMode", "()I", - (void*)android_server_UsbDeviceManager_getAudioMode }, - { "nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;", - (void*)android_server_UsbDeviceManager_openControl }, + {"nativeGetAccessoryStrings", "()[Ljava/lang/String;", + (void *)android_server_UsbDeviceManager_getAccessoryStrings}, + {"nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;", + (void *)android_server_UsbDeviceManager_openAccessory}, + {"nativeIsStartRequested", "()Z", (void *)android_server_UsbDeviceManager_isStartRequested}, + {"nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;", + (void *)android_server_UsbDeviceManager_openControl}, }; int register_android_server_UsbDeviceManager(JNIEnv *env) diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index b38a2f9558e9..d0df2b20721b 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -168,6 +168,10 @@ <xs:element type="nonNegativeDecimal" name="screenBrightnessCapForWearBedtimeMode"> <xs:annotation name="final"/> </xs:element> + <!-- Timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. --> + <xs:element type="idleScreenRefreshRateTimeout" name="idleScreenRefreshRateTimeout" minOccurs="0"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> @@ -772,6 +776,30 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="idleScreenRefreshRateTimeout"> + <xs:element name="luxThresholds" type="idleScreenRefreshRateTimeoutLuxThresholds" minOccurs="0"> + <xs:annotation name="final"/> + </xs:element> + </xs:complexType> + + <!-- Lux based timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. --> + <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholds"> + <xs:sequence> + <xs:element name="point" type="idleScreenRefreshRateTimeoutLuxThresholdPoint" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + + <!-- Represents a tuple of lux and timeout(in ms), which represents the timeout value for the lux in + the [luxValue, nextLuxValue (INF if missing)) --> + <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholdPoint"> + <xs:element name="lux" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="timeout" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + </xs:complexType> + <!-- Predefined type names as defined by AutomaticBrightnessController.AutomaticBrightnessMode --> <xs:simpleType name="AutoBrightnessModeName"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index b329db4a2076..00dc90828d90 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -111,6 +111,7 @@ package com.android.server.display.config { method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle(); method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); + method public final com.android.server.display.config.IdleScreenRefreshRateTimeout getIdleScreenRefreshRateTimeout(); method public final com.android.server.display.config.SensorDetails getLightSensor(); method public com.android.server.display.config.LuxThrottling getLuxThrottling(); method @Nullable public final String getName(); @@ -146,6 +147,7 @@ package com.android.server.display.config { method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds); method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); + method public final void setIdleScreenRefreshRateTimeout(com.android.server.display.config.IdleScreenRefreshRateTimeout); method public final void setLightSensor(com.android.server.display.config.SensorDetails); method public void setLuxThrottling(com.android.server.display.config.LuxThrottling); method public final void setName(@Nullable String); @@ -222,6 +224,25 @@ package com.android.server.display.config { method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal); } + public class IdleScreenRefreshRateTimeout { + ctor public IdleScreenRefreshRateTimeout(); + method public final com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds getLuxThresholds(); + method public final void setLuxThresholds(com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds); + } + + public class IdleScreenRefreshRateTimeoutLuxThresholdPoint { + ctor public IdleScreenRefreshRateTimeoutLuxThresholdPoint(); + method public final java.math.BigInteger getLux(); + method public final java.math.BigInteger getTimeout(); + method public final void setLux(java.math.BigInteger); + method public final void setTimeout(java.math.BigInteger); + } + + public class IdleScreenRefreshRateTimeoutLuxThresholds { + ctor public IdleScreenRefreshRateTimeoutLuxThresholds(); + method public java.util.List<com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint> getPoint(); + } + public class IntegerArray { ctor public IntegerArray(); method public java.util.List<java.math.BigInteger> getItem(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 2867041511b5..35b69f812ff0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -54,6 +54,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.server.display.config.HdrBrightnessData; +import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.ThermalStatus; import com.android.server.display.feature.DisplayManagerFlags; @@ -108,6 +109,7 @@ public final class DisplayDeviceConfigTest { when(mContext.getResources()).thenReturn(mResources); when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true); when(mFlags.isSensorBasedBrightnessThrottlingEnabled()).thenReturn(true); + when(mFlags.isIdleScreenRefreshRateTimeoutEnabled()).thenReturn(true); mockDeviceConfigs(); } @@ -146,6 +148,8 @@ public final class DisplayDeviceConfigTest { assertNull(mDisplayDeviceConfig.getProximitySensor().type); assertNull(mDisplayDeviceConfig.getProximitySensor().name); assertEquals(TEMPERATURE_TYPE_SKIN, mDisplayDeviceConfig.getTempSensor().type); + assertEquals(List.of(), mDisplayDeviceConfig + .getIdleScreenRefreshRateTimeoutLuxThresholdPoint()); assertNull(mDisplayDeviceConfig.getTempSensor().name); assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable()); } @@ -226,6 +230,19 @@ public final class DisplayDeviceConfigTest { assertNotNull(mDisplayDeviceConfig.getHostUsiVersion()); assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2); assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0); + + List<IdleScreenRefreshRateTimeoutLuxThresholdPoint> + idleScreenRefreshRateTimeoutLuxThresholdPoints = + mDisplayDeviceConfig.getIdleScreenRefreshRateTimeoutLuxThresholdPoint(); + assertEquals(2, idleScreenRefreshRateTimeoutLuxThresholdPoints.size()); + assertEquals(6, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0).getLux() + .intValue()); + assertEquals(1000, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0) + .getTimeout().intValue()); + assertEquals(10, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1) + .getLux().intValue()); + assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1) + .getTimeout().intValue()); } @Test @@ -734,6 +751,8 @@ public final class DisplayDeviceConfigTest { assertEquals(brightnessIntToFloat(35), mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA); + assertEquals(List.of(), mDisplayDeviceConfig + .getIdleScreenRefreshRateTimeoutLuxThresholdPoint()); } @Test @@ -1587,6 +1606,18 @@ public final class DisplayDeviceConfigTest { + "<screenBrightnessCapForWearBedtimeMode>" + "0.1" + "</screenBrightnessCapForWearBedtimeMode>" + + "<idleScreenRefreshRateTimeout>" + + "<luxThresholds>" + + "<point>" + + "<lux>6</lux>" + + "<timeout>1000</timeout>" + + "</point>" + + "<point>" + + "<lux>10</lux>" + + "<timeout>800</timeout>" + + "</point>" + + "</luxThresholds>" + + "</idleScreenRefreshRateTimeout>" + "</displayConfiguration>\n"; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 48fc4078999d..869cec8d733d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -78,6 +78,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.hardware.Sensor; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; @@ -721,7 +722,9 @@ public class DisplayManagerServiceTest { IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback( displayManager); - listener.onStateChanged(123); + listener.onDeviceStateChanged(new DeviceState( + new DeviceState.Configuration.Builder(123 /* identifier */, + "TEST" /* name */).build())); waitForIdleHandler(handler); InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 2939192d3d2e..d0c7077f29c0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -650,7 +650,6 @@ public class LogicalDisplayMapperTest { public void testDeviceShouldBePutToSleep() { assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, DEVICE_STATE_OPEN, - /* isOverrideActive= */false, /* isInteractive= */true, /* isBootCompleted= */true)); } @@ -661,7 +660,6 @@ public class LogicalDisplayMapperTest { assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, DEVICE_STATE_OPEN, - /* isOverrideActive= */false, /* isInteractive= */true, /* isBootCompleted= */true)); } @@ -670,21 +668,10 @@ public class LogicalDisplayMapperTest { public void testDeviceShouldNotBePutToSleep() { assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN, DEVICE_STATE_CLOSED, - /* isOverrideActive= */false, /* isInteractive= */true, /* isBootCompleted= */true)); assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, INVALID_DEVICE_STATE_IDENTIFIER, - /* isOverrideActive= */false, - /* isInteractive= */true, - /* isBootCompleted= */true)); - } - - @Test - public void testDeviceShouldNotBePutToSleepDifferentBaseState() { - assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, - DEVICE_STATE_OPEN, - /* isOverrideActive= */true, /* isInteractive= */true, /* isBootCompleted= */true)); } @@ -750,7 +737,7 @@ public class LogicalDisplayMapperTest { // We can only have one default display assertEquals(DEFAULT_DISPLAY, id(display1)); - mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLogicalDisplayMapper.setDeviceStateLocked(0); advanceTime(1000); // The new state is not applied until the boot is completed assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); @@ -771,7 +758,7 @@ public class LogicalDisplayMapperTest { assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2) .getDisplayInfoLocked().thermalBrightnessThrottlingDataId); - mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mLogicalDisplayMapper.setDeviceStateLocked(1); advanceTime(1000); assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); @@ -784,7 +771,7 @@ public class LogicalDisplayMapperTest { mLogicalDisplayMapper.getDisplayLocked(device2) .getDisplayInfoLocked().thermalBrightnessThrottlingDataId); - mLogicalDisplayMapper.setDeviceStateLocked(2, false); + mLogicalDisplayMapper.setDeviceStateLocked(2); advanceTime(1000); assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); @@ -861,7 +848,7 @@ public class LogicalDisplayMapperTest { // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state // 4) Dispatch handler events. mLogicalDisplayMapper.onBootCompleted(); - mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLogicalDisplayMapper.setDeviceStateLocked(0); mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); advanceTime(1000); final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked( @@ -891,7 +878,7 @@ public class LogicalDisplayMapperTest { /* includeDisabled= */ false)); // Now do it again to go back to state 1 - mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mLogicalDisplayMapper.setDeviceStateLocked(1); mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); advanceTime(1000); final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked( @@ -945,7 +932,7 @@ public class LogicalDisplayMapperTest { // We can only have one default display assertEquals(DEFAULT_DISPLAY, id(display1)); - mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLogicalDisplayMapper.setDeviceStateLocked(0); advanceTime(1000); mLogicalDisplayMapper.onBootCompleted(); advanceTime(1000); @@ -964,11 +951,11 @@ public class LogicalDisplayMapperTest { ///////////////// private void finishBootAndFoldDevice() { - mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN, false); + mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN); advanceTime(1000); mLogicalDisplayMapper.onBootCompleted(); advanceTime(1000); - mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED, false); + mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED); advanceTime(1000); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt index 638924eeb2a3..b182ccef091e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt @@ -95,9 +95,9 @@ class BrightnessObserverTest { ) { ALL_ENABLED(true, true, CombinedVote( listOf(DisableRefreshRateSwitchingVote(true), - SupportedModesVote( - listOf(SupportedModesVote.SupportedMode(60f, 60f), - SupportedModesVote.SupportedMode(120f, 120f)))))), + SupportedRefreshRatesVote( + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f), + SupportedRefreshRatesVote.RefreshRates(120f, 120f)))))), VRR_NOT_SUPPORTED(false, true, DisableRefreshRateSwitchingVote(true)), VSYNC_VOTE_DISABLED(true, false, DisableRefreshRateSwitchingVote(true)) } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java new file mode 100644 index 000000000000..e93e5bc63dd8 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.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.server.display.mode; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManagerInternal; +import android.util.SparseArray; +import android.view.Display; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.server.sensors.SensorManagerInternal; + +import junitparams.JUnitParamsRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(JUnitParamsRunner.class) +public class ProximitySensorObserverTest { + + private static final float FLOAT_TOLERANCE = 0.01f; + private static final int DISPLAY_ID = 1; + private static final SurfaceControl.RefreshRateRange REFRESH_RATE_RANGE = + new SurfaceControl.RefreshRateRange(60, 90); + + private final VotesStorage mStorage = new VotesStorage(() -> { }, null); + private final FakesInjector mInjector = new FakesInjector(); + private ProximitySensorObserver mSensorObserver; + + @Mock + DisplayManagerInternal mMockDisplayManagerInternal; + @Mock + SensorManagerInternal mMockSensorManagerInternal; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockDisplayManagerInternal.getRefreshRateForDisplayAndSensor(eq(DISPLAY_ID), + any(), any())).thenReturn(REFRESH_RATE_RANGE); + mSensorObserver = new ProximitySensorObserver(mStorage, mInjector); + mSensorObserver.observe(); + } + + @Test + public void testAddsProximityVoteIfSensorManagerProximityActive() { + mSensorObserver.onProximityActive(true); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(1); + Vote vote = displayVotes.get(Vote.PRIORITY_PROXIMITY); + assertThat(vote).isNotNull(); + assertThat(vote).isInstanceOf(CombinedVote.class); + CombinedVote combinedVote = (CombinedVote) vote; + RefreshRateVote.PhysicalVote physicalVote = + (RefreshRateVote.PhysicalVote) combinedVote.mVotes.get(0); + assertThat(physicalVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(physicalVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); + } + + @Test + public void testDoesNotAddProximityVoteIfSensorManagerProximityNotActive() { + mSensorObserver.onProximityActive(false); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(0); + } + + @Test + public void testDoesNotAddProximityVoteIfDoze() { + mInjector.mDozeState = true; + mSensorObserver.onDisplayChanged(DISPLAY_ID); + mSensorObserver.onProximityActive(true); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(0); + } + + private class FakesInjector extends DisplayModeDirectorTest.FakesInjector { + + private boolean mDozeState = false; + + @Override + public Display[] getDisplays() { + return new Display[] { createDisplay(DISPLAY_ID) }; + } + + @Override + public DisplayManagerInternal getDisplayManagerInternal() { + return mMockDisplayManagerInternal; + } + + @Override + public SensorManagerInternal getSensorManagerInternal() { + return mMockSensorManagerInternal; + } + + @Override + public boolean isDozeState(Display d) { + return mDozeState; + } + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt index ebb4f1889cd6..230317ba738b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt @@ -85,9 +85,9 @@ class SettingsObserverTest { internal val expectedVote: Vote? ) { ALL_ENABLED(true, true, true, - SupportedModesVote(listOf( - SupportedModesVote.SupportedMode(60f, 240f), - SupportedModesVote.SupportedMode(60f, 60f) + SupportedRefreshRatesVote(listOf( + SupportedRefreshRatesVote.RefreshRates(60f, 240f), + SupportedRefreshRatesVote.RefreshRates(60f, 60f) ))), LOW_POWER_OFF(true, true, false, null), DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true, diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt index 04e626536eba..6ce49b8cb31e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,12 +27,9 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SupportedModesVoteTest { - private val supportedModes = listOf( - SupportedModesVote.SupportedMode(60f, 90f ), - SupportedModesVote.SupportedMode(120f, 240f ) - ) + private val supportedModes = listOf(1, 2, 4) - private val otherMode = SupportedModesVote.SupportedMode(120f, 120f ) + private val otherMode = 5 private lateinit var supportedModesVote: SupportedModesVote @@ -42,31 +39,31 @@ class SupportedModesVoteTest { } @Test - fun `adds supported modes if supportedModes in summary is null`() { + fun `adds supported mode ids if supportedModeIds in summary is null`() { val summary = createVotesSummary() supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).containsExactlyElementsIn(supportedModes) + assertThat(summary.supportedModeIds).containsExactlyElementsIn(supportedModes) } @Test - fun `does not add supported modes if summary has empty list of modes`() { + fun `does not add supported mode ids if summary has empty list of modeIds`() { val summary = createVotesSummary() - summary.supportedModes = ArrayList() + summary.supportedModeIds = ArrayList() supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).isEmpty() + assertThat(summary.supportedModeIds).isEmpty() } @Test fun `filters out modes that does not match vote`() { val summary = createVotesSummary() - summary.supportedModes = ArrayList(listOf(otherMode, supportedModes[0])) + summary.supportedModeIds = ArrayList(listOf(otherMode, supportedModes[0])) supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).containsExactly(supportedModes[0]) + assertThat(summary.supportedModeIds).containsExactly(supportedModes[0]) } }
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt new file mode 100644 index 000000000000..d0c112be24a2 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt @@ -0,0 +1,72 @@ +/* + * 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.server.display.mode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SupportedRefreshRatesVoteTest { + private val refreshRates = listOf( + SupportedRefreshRatesVote.RefreshRates(60f, 90f), + SupportedRefreshRatesVote.RefreshRates(120f, 240f) + ) + + private val otherMode = SupportedRefreshRatesVote.RefreshRates(120f, 120f) + + private lateinit var supportedRefreshRatesVote: SupportedRefreshRatesVote + + @Before + fun setUp() { + supportedRefreshRatesVote = SupportedRefreshRatesVote(refreshRates) + } + + @Test + fun `adds supported refresh rates if supportedModes in summary is null`() { + val summary = createVotesSummary() + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).containsExactlyElementsIn(refreshRates) + } + + @Test + fun `does not add supported refresh rates if summary has empty list of refresh rates`() { + val summary = createVotesSummary() + summary.supportedRefreshRates = ArrayList() + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).isEmpty() + } + + @Test + fun `filters out supported refresh rates that does not match vote`() { + val summary = createVotesSummary() + summary.supportedRefreshRates = ArrayList(listOf(otherMode, refreshRates[0])) + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).containsExactly(refreshRates[0]) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt new file mode 100644 index 000000000000..c49205bcfe3d --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode + +import android.os.IBinder +import android.os.RemoteException +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +private const val DISPLAY_ID = 1 +private const val DISPLAY_ID_OTHER = 2 + +@SmallTest +@RunWith(TestParameterInjector::class) +class SystemRequestObserverTest { + + + @get:Rule + val mockitoRule = MockitoJUnit.rule() + + private val mockToken = mock<IBinder>() + private val mockOtherToken = mock<IBinder>() + + private val storage = VotesStorage({}, null) + + @Test + fun `requestDisplayModes adds vote to storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size) + for (mode in requestedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `requestDisplayModes overrides votes in storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, intArrayOf(1, 2, 3)) + + val overrideModes = intArrayOf(10, 20, 30) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, overrideModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(overrideModes.size) + for (mode in overrideModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `requestDisplayModes removes vote to storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `requestDisplayModes calls linkToDeath to token`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + verify(mockToken).linkToDeath(any(), eq(0)) + } + + @Test + fun `does not add votes to storage if binder died when requestDisplayModes called`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + doThrow(RemoteException()).whenever(mockOtherToken).linkToDeath(any(), eq(0)) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `removes all votes from storage when binder dies`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>() + verify(mockToken).linkToDeath(deathRecipientCaptor.capture(), eq(0)) + + deathRecipientCaptor.lastValue.binderDied(mockToken) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `calls unlinkToDeath on token when no votes remaining`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + verify(mockToken).unlinkToDeath(any(), eq(0)) + } + + @Test + fun `does not call unlinkToDeath on token when votes for other display in storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID_OTHER, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + verify(mockToken, never()).unlinkToDeath(any(), eq(0)) + } + + @Test + fun `requestDisplayModes subset modes from different tokens`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val requestedOtherModes = intArrayOf(2, 3, 4) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes) + + verify(mockToken).linkToDeath(any(), eq(0)) + verify(mockOtherToken).linkToDeath(any(), eq(0)) + verify(mockToken, never()).unlinkToDeath(any(), eq(0)) + verify(mockOtherToken, never()).unlinkToDeath(any(), eq(0)) + + val expectedModes = intArrayOf(2, 3) + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(expectedModes.size) + for (mode in expectedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `recalculates vote if one binder dies`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val requestedOtherModes = intArrayOf(2, 3, 4) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes) + + val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>() + verify(mockOtherToken).linkToDeath(deathRecipientCaptor.capture(), eq(0)) + deathRecipientCaptor.lastValue.binderDied(mockOtherToken) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size) + for (mode in requestedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt index 910e03c5db85..6b90bde188c5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt @@ -18,10 +18,10 @@ package com.android.server.display.mode internal fun createVotesSummary( isDisplayResolutionRangeVotingEnabled: Boolean = true, - vsyncProximityVoteEnabled: Boolean = true, + supportedModesVoteEnabled: Boolean = true, loggingEnabled: Boolean = true, supportsFrameRateOverride: Boolean = true ): VoteSummary { - return VoteSummary(isDisplayResolutionRangeVotingEnabled, vsyncProximityVoteEnabled, + return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled, loggingEnabled, supportsFrameRateOverride) }
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt index d6c84690e65f..04b35f10545f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt @@ -28,29 +28,29 @@ import org.junit.runner.RunWith @RunWith(TestParameterInjector::class) class VoteSummaryTest { - enum class SupportedModesVoteTestCase( - val vsyncProximityVoteEnabled: Boolean, - internal val summarySupportedModes: List<SupportedModesVote.SupportedMode>?, + enum class SupportedRefreshRatesTestCase( + val supportedModesVoteEnabled: Boolean, + internal val summaryRefreshRates: List<SupportedRefreshRatesVote.RefreshRates>?, val modesToFilter: Array<Display.Mode>, val expectedModeIds: List<Int> ) { HAS_NO_MATCHING_VOTE(true, - listOf(SupportedModesVote.SupportedMode(60f, 60f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), listOf() ), HAS_SINGLE_MATCHING_VOTE(true, - listOf(SupportedModesVote.SupportedMode(60f, 90f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), listOf(3) ), HAS_MULTIPLE_MATCHING_VOTES(true, - listOf(SupportedModesVote.SupportedMode(60f, 90f), - SupportedModesVote.SupportedMode(90f, 90f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f), + SupportedRefreshRatesVote.RefreshRates(90f, 90f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), @@ -70,7 +70,69 @@ class VoteSummaryTest { createMode(3, 60f, 90f)), listOf(1, 2, 3) ), - HAS_VSYNC_PROXIMITY_DISABLED(false, + HAS_SUPPORTED_MODES_VOTE_DISABLED(false, + listOf(), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 2, 3) + ), + } + + @Test + fun `filters modes for summary supportedRefreshRates`( + @TestParameter testCase: SupportedRefreshRatesTestCase + ) { + val summary = createSummary(testCase.supportedModesVoteEnabled) + summary.supportedRefreshRates = testCase.summaryRefreshRates + + val result = summary.filterModes(testCase.modesToFilter) + + assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds) + } + + enum class SupportedModesTestCase( + val supportedModesVoteEnabled: Boolean, + internal val summarySupportedModes: List<Int>?, + val modesToFilter: Array<Display.Mode>, + val expectedModeIds: List<Int> + ) { + HAS_NO_MATCHING_VOTE(true, + listOf(4, 5), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf() + ), + HAS_SINGLE_MATCHING_VOTE(true, + listOf(3), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(3) + ), + HAS_MULTIPLE_MATCHING_VOTES(true, + listOf(1, 3), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 3) + ), + HAS_NO_SUPPORTED_MODES(true, + listOf(), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf() + ), + HAS_NULL_SUPPORTED_MODES(true, + null, + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 2, 3) + ), + HAS_SUPPORTED_MODES_VOTE_DISABLED(false, listOf(), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), @@ -81,10 +143,10 @@ class VoteSummaryTest { @Test fun `filters modes for summary supportedModes`( - @TestParameter testCase: SupportedModesVoteTestCase + @TestParameter testCase: SupportedModesTestCase ) { - val summary = createSummary(testCase.vsyncProximityVoteEnabled) - summary.supportedModes = testCase.summarySupportedModes + val summary = createSummary(testCase.supportedModesVoteEnabled) + summary.supportedModeIds = testCase.summarySupportedModes val result = summary.filterModes(testCase.modesToFilter) @@ -96,8 +158,8 @@ private fun createMode(modeId: Int, refreshRate: Float, vsyncRate: Float): Displ FloatArray(0), IntArray(0)) } -private fun createSummary(vsyncVoteEnabled: Boolean): VoteSummary { - val summary = createVotesSummary(vsyncProximityVoteEnabled = vsyncVoteEnabled) +private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary { + val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled) summary.width = 600 summary.height = 800 summary.maxPhysicalRefreshRate = Float.POSITIVE_INFINITY diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java index 1f6f1a41bea7..a248d6de118f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java @@ -18,6 +18,7 @@ package com.android.server.display.mode; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -153,4 +154,51 @@ public class VotesStorageTest { assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0); verify(mVotesListener, never()).onChanged(); } + + + @Test + public void removesAllVotesForPriority() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER); + // WHEN removeAllVotesForPriority is called + mVotesStorage.removeAllVotesForPriority(PRIORITY); + // THEN votes with priority are removed from the storage + SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isNull(); + votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isNull(); + } + + @Test + public void removesAllVotesForPriority_notifiesListenerOnce() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER); + clearInvocations(mVotesListener); + // WHEN removeAllVotesForPriority is called + mVotesStorage.removeAllVotesForPriority(PRIORITY); + // THEN listener notified once + verify(mVotesListener).onChanged(); + } + + @Test + public void removesAllVotesForPriority_noChangesIfNothingRemoved() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + clearInvocations(mVotesListener); + // WHEN removeAllVotesForPriority is called for missing priority + mVotesStorage.removeAllVotesForPriority(PRIORITY_OTHER); + // THEN no changes to votes storage + SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE); + verify(mVotesListener, never()).onChanged(); + } } diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index aec896f383c4..f86ff14218ba 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -64,6 +64,7 @@ import android.content.IntentFilter; import android.content.PermissionChecker; import android.content.res.Resources; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.hardware.display.AmbientDisplayConfiguration; @@ -154,6 +155,8 @@ public class PowerManagerServiceTest { private static final float BRIGHTNESS_FACTOR = 0.7f; private static final boolean BATTERY_SAVER_ENABLED = true; + private static final DeviceState DEVICE_STATE_1 = new DeviceState( + new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build()); @Mock private BatterySaverController mBatterySaverControllerMock; @Mock private BatterySaverPolicy mBatterySaverPolicyMock; @@ -2839,7 +2842,7 @@ public class PowerManagerServiceTest { // Send a display state change event and advance the clock 10. final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue(); - deviceStateCallback.onStateChanged(1); + deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1); final long timeToAdvance = 10; advanceTime(timeToAdvance); @@ -2849,7 +2852,7 @@ public class PowerManagerServiceTest { assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse(); // Send the same state and ensure that does not trigger an update. - deviceStateCallback.onStateChanged(1); + deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1); advanceTime(timeToAdvance); final long newTime = timeToAdvance * 2; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 03b695d170ad..43b424fab907 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1376,8 +1376,9 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(w1.syncNextBuffer()); assertTrue(w2.syncNextBuffer()); - // A drawn window can complete the sync state automatically. + // A drawn window in non-explicit sync can complete the sync state automatically. w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; + w1.mPrepareSyncSeqId = 0; makeLastConfigReportedToClient(w1, true /* visible */); mWm.mSyncEngine.onSurfacePlacement(); verify(mockCallback).onTransactionReady(anyInt(), any()); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 77b263824b78..9acda5f249d2 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -142,8 +142,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser "/sys/class/android_usb/android0/state"; private static final String RNDIS_ETH_ADDR_PATH = "/sys/class/android_usb/android0/f_rndis/ethaddr"; - private static final String AUDIO_SOURCE_PCM_PATH = - "/sys/class/android_usb/android0/f_audio_source/pcm"; private static final String MIDI_ALSA_PATH = "/sys/class/android_usb/android0/f_midi/alsa"; @@ -172,8 +170,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private static final int MSG_UPDATE_USB_SPEED = 22; private static final int MSG_UPDATE_HAL_VERSION = 23; - private static final int AUDIO_MODE_SOURCE = 1; - // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, // which need debouncing. @@ -464,7 +460,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser int operationId = sUsbOperationCount.incrementAndGet(); mAccessoryStrings = nativeGetAccessoryStrings(); - boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE); // don't start accessory mode if our mandatory strings have not been set boolean enableAccessory = (mAccessoryStrings != null && mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null && @@ -474,9 +469,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser if (enableAccessory) { functions |= UsbManager.FUNCTION_ACCESSORY; } - if (enableAudio) { - functions |= UsbManager.FUNCTION_AUDIO_SOURCE; - } if (functions != UsbManager.FUNCTION_NONE) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_MODE_ENTER_TIMEOUT), @@ -2490,6 +2482,4 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private native FileDescriptor nativeOpenControl(String usbFunction); private native boolean nativeIsStartRequested(); - - private native int nativeGetAudioMode(); } |