diff options
252 files changed, 4579 insertions, 1573 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 2f843f9d6164..8547ec164084 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -1809,12 +1809,25 @@ aconfig_declarations { name: "aconfig_settingslib_flags", package: "com.android.settingslib.flags", container: "system", + exportable: true, srcs: [ "packages/SettingsLib/aconfig/settingslib.aconfig", ], } java_aconfig_library { + name: "aconfig_settingslib_exported_flags_java_lib", + aconfig_declarations: "aconfig_settingslib_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + mode: "exported", + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.permission", + ], +} + +java_aconfig_library { name: "aconfig_settingslib_flags_java_lib", aconfig_declarations: "aconfig_settingslib_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], diff --git a/MEMORY_OWNERS b/MEMORY_OWNERS index 89ce5140d8ea..12aa2951bbc9 100644 --- a/MEMORY_OWNERS +++ b/MEMORY_OWNERS @@ -2,5 +2,4 @@ surenb@google.com tjmercier@google.com kaleshsingh@google.com jyescas@google.com -carlosgalo@google.com jji@google.com diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index abdfb53537f8..999db18a1229 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -485,6 +485,11 @@ public abstract class ActivityManagerInternal { */ public static final int OOM_ADJ_REASON_FOLLOW_UP = 23; + /** + * Oom Adj Reason: Update after oom adjuster configuration has changed. + */ + public static final int OOM_ADJ_REASON_RECONFIGURATION = 24; + @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = { OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, @@ -510,6 +515,7 @@ public abstract class ActivityManagerInternal { OOM_ADJ_REASON_RESTRICTION_CHANGE, OOM_ADJ_REASON_COMPONENT_DISABLED, OOM_ADJ_REASON_FOLLOW_UP, + OOM_ADJ_REASON_RECONFIGURATION, }) @Retention(RetentionPolicy.SOURCE) public @interface OomAdjReason {} diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 61b56877589b..599f1a8be233 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -27,6 +27,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * Stores App Compat information about a particular Task. @@ -58,16 +59,11 @@ public class AppCompatTaskInfo implements Parcelable { public int topActivityLetterboxHeight = PROPERTY_VALUE_UNSET; /** - * Contains the current app height of the letterboxed activity if available or - * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise. + * Contains the app bounds of the top activity or size compat mode + * bounds when in size compat mode. If null, contains bounds. */ - public int topActivityLetterboxAppHeight = PROPERTY_VALUE_UNSET; - - /** - * Contains the current app width of the letterboxed activity if available or - * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise. - */ - public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET; + @NonNull + public final Rect topActivityAppBounds = new Rect(); /** * Contains the top activity bounds when the activity is letterboxed. @@ -350,8 +346,7 @@ public class AppCompatTaskInfo implements Parcelable { && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight - && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth - && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight + && topActivityAppBounds.equals(that.topActivityAppBounds) && topActivityLetterboxHorizontalPosition == that.topActivityLetterboxHorizontalPosition && cameraCompatTaskInfo.equalsForTaskOrganizer(that.cameraCompatTaskInfo); @@ -371,8 +366,7 @@ public class AppCompatTaskInfo implements Parcelable { == that.topActivityLetterboxHorizontalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight - && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth - && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight + && topActivityAppBounds.equals(that.topActivityAppBounds) && cameraCompatTaskInfo.equalsForCompatUi(that.cameraCompatTaskInfo); } @@ -385,8 +379,7 @@ public class AppCompatTaskInfo implements Parcelable { topActivityLetterboxHorizontalPosition = source.readInt(); topActivityLetterboxWidth = source.readInt(); topActivityLetterboxHeight = source.readInt(); - topActivityLetterboxAppWidth = source.readInt(); - topActivityLetterboxAppHeight = source.readInt(); + topActivityAppBounds.set(Objects.requireNonNull(source.readTypedObject(Rect.CREATOR))); topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR); cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR); } @@ -401,8 +394,7 @@ public class AppCompatTaskInfo implements Parcelable { dest.writeInt(topActivityLetterboxHorizontalPosition); dest.writeInt(topActivityLetterboxWidth); dest.writeInt(topActivityLetterboxHeight); - dest.writeInt(topActivityLetterboxAppWidth); - dest.writeInt(topActivityLetterboxAppHeight); + dest.writeTypedObject(topActivityAppBounds, flags); dest.writeTypedObject(topActivityLetterboxBounds, flags); dest.writeTypedObject(cameraCompatTaskInfo, flags); } @@ -421,8 +413,7 @@ public class AppCompatTaskInfo implements Parcelable { + topActivityLetterboxHorizontalPosition + " topActivityLetterboxWidth=" + topActivityLetterboxWidth + " topActivityLetterboxHeight=" + topActivityLetterboxHeight - + " topActivityLetterboxAppWidth=" + topActivityLetterboxAppWidth - + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight + + " topActivityAppBounds=" + topActivityAppBounds + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled() + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled() + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride() diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 8ed66eb7e6c0..e9b889a2f1aa 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -55,6 +55,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; @@ -677,9 +678,14 @@ public class NotificationManager { } /** {@hide} */ - @UnsupportedAppUsage - public NotificationManager(Context context, InstantSource clock) + public NotificationManager(Context context) { + this(context, SystemClock.elapsedRealtimeClock()); + } + + /** {@hide} */ + @UnsupportedAppUsage + public NotificationManager(Context context, InstantSource clock) { mContext = context; mClock = clock; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 920b19cd8f78..0bbe9434293a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -17,7 +17,7 @@ package android.app; import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; -import static android.provider.flags.Flags.stageFlagsForBuild; +import static android.provider.flags.Flags.newStoragePublicApi; import static android.server.Flags.removeGameManagerServiceFromWear; import android.accounts.AccountManager; @@ -289,7 +289,6 @@ import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; import com.android.internal.util.Preconditions; -import java.time.InstantSource; import java.util.Map; import java.util.Objects; @@ -625,8 +624,8 @@ public final class SystemServiceRegistry { com.android.internal.R.style.Theme_Dialog, com.android.internal.R.style.Theme_Holo_Dialog, com.android.internal.R.style.Theme_DeviceDefault_Dialog, - com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)), - InstantSource.system()); + com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)) + ); }}); registerService(Context.PEOPLE_SERVICE, PeopleManager.class, @@ -1841,7 +1840,7 @@ public final class SystemServiceRegistry { VirtualizationFrameworkInitializer.registerServiceWrappers(); ConnectivityFrameworkInitializerBaklava.registerServiceWrappers(); - if (stageFlagsForBuild()) { + if (newStoragePublicApi()) { ConfigInfrastructureFrameworkInitializer.registerServiceWrappers(); } diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 0d219a901b9d..4c4753872c03 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -152,7 +152,7 @@ flag { name: "cache_sdk_system_features" namespace: "system_performance" description: "Feature flag to enable optimized cache for SDK-defined system feature lookups." - bug: "375000483" + bug: "326623529" } flag { diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 852f04793f15..9c6b71b72ec8 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -417,6 +417,7 @@ public abstract class CameraDevice implements AutoCloseable { * or if any of the output configurations sets a stream use * case different from {@link * android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT}. + * @throws UnsupportedOperationException if the camera has been opened in shared mode * @see CameraExtensionCharacteristics#getSupportedExtensions * @see CameraExtensionCharacteristics#getExtensionSupportedSizes */ @@ -1258,7 +1259,8 @@ public abstract class CameraDevice implements AutoCloseable { * configurations are empty; or the session configuration * executor is invalid; * or the output dynamic range combination is - * invalid/unsupported. + * invalid/unsupported; or the session type is not shared when + * camera has been opened in shared mode. * @throws CameraAccessException In case the camera device is no longer connected or has * encountered a fatal error. * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler) @@ -1292,6 +1294,8 @@ public abstract class CameraDevice implements AutoCloseable { * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera device has been closed + * @throws UnsupportedOperationException if this is not a primary client of a camera opened in + * shared mode */ @NonNull public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) @@ -1328,6 +1332,8 @@ public abstract class CameraDevice implements AutoCloseable { * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera device has been closed + * @throws UnsupportedOperationException if this is not a primary client of a camera opened in + * shared mode * * @see #TEMPLATE_PREVIEW * @see #TEMPLATE_RECORD @@ -1369,6 +1375,7 @@ public abstract class CameraDevice implements AutoCloseable { * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera device has been closed + * @throws UnsupportedOperationException if the camera has been opened in shared mode * * @see CaptureRequest.Builder * @see TotalCaptureResult diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index aba2345f28d8..bfaff941939c 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1375,6 +1375,9 @@ public final class CameraManager { * @throws SecurityException if the application does not have permission to * access the camera * + * @throws UnsupportedOperationException if {@link #isCameraDeviceSharingSupported} returns + * false for the given {@code cameraId}. + * * @see #getCameraIdList * @see android.app.admin.DevicePolicyManager#setCameraDisabled * @@ -1393,6 +1396,10 @@ public final class CameraManager { if (executor == null) { throw new IllegalArgumentException("executor was null"); } + if (!isCameraDeviceSharingSupported(cameraId)) { + throw new UnsupportedOperationException( + "CameraDevice sharing is not supported for Camera ID: " + cameraId); + } openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0, getRotationOverride(mContext), /*sharedMode*/true); } diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 496d316eb028..1c65b0882e0f 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -299,6 +299,24 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> return mRequestType; } + /** + * Get the stream ids corresponding to the target surfaces. + * + * @hide + */ + public int[] getStreamIds() { + return mStreamIdxArray; + }; + + /** + * Get the surface ids corresponding to the target surfaces. + * + * @hide + */ + public int[] getSurfaceIds() { + return mSurfaceIdxArray; + }; + // If this request is part of constrained high speed request list that was created by // {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList} private boolean mIsPartOfCHSRequestList = false; diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index ce8661e90978..7e0456b22be8 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -340,6 +340,30 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } } + /** + * Shared Camera capture session API which can be used by the clients + * to start streaming. + * + * @hide + */ + public int startStreaming(List<Surface> surfaces, Executor executor, + CaptureCallback callback) throws CameraAccessException { + + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); + + executor = CameraDeviceImpl.checkExecutor(executor, callback); + + if (DEBUG) { + Log.v(TAG, mIdString + "startStreaming callback " + callback + " executor" + + " " + executor); + } + + return addPendingSequence(mDeviceImpl.startStreaming(surfaces, + createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor)); + } + } + private void checkRepeatingRequest(CaptureRequest request) { if (request == null) { throw new IllegalArgumentException("request must not be null"); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 321f09bd4760..89a6b02b56c4 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -451,6 +451,16 @@ public class CameraDeviceImpl extends CameraDevice } } + /** + * When camera device is opened in shared mode, call to check if this is a primary client. + * + */ + public boolean isPrimaryClient() { + synchronized (mInterfaceLock) { + return mIsPrimaryClient; + } + } + private Map<String, CameraCharacteristics> getPhysicalIdToChars() { if (mPhysicalIdsToChars == null) { try { @@ -858,24 +868,19 @@ public class CameraDeviceImpl extends CameraDevice List<SharedOutputConfiguration> sharedConfigs = sharedSessionConfiguration.getOutputStreamsInformation(); for (SharedOutputConfiguration sharedConfig : sharedConfigs) { - if (outConfig.getConfiguredSize().equals(sharedConfig.getSize()) - && (outConfig.getConfiguredFormat() == sharedConfig.getFormat()) - && (outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE) - && (outConfig.getSurfaceType() == sharedConfig.getSurfaceType()) + if ((outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE) && (outConfig.getMirrorMode() == sharedConfig.getMirrorMode()) - && (outConfig.getUsage() == sharedConfig.getUsage()) && (outConfig.isReadoutTimestampEnabled() == sharedConfig.isReadoutTimestampEnabled()) && (outConfig.getTimestampBase() == sharedConfig.getTimestampBase()) && (outConfig.getStreamUseCase() == sharedConfig.getStreamUseCase()) - && (outConfig.getColorSpace().equals( - sharedSessionConfiguration.getColorSpace())) && (outConfig.getDynamicRangeProfile() == DynamicRangeProfiles.STANDARD) - && (outConfig.getConfiguredDataspace() == sharedConfig.getDataspace()) && (Objects.equals(outConfig.getPhysicalCameraId(), sharedConfig.getPhysicalCameraId())) && (outConfig.getSensorPixelModes().isEmpty()) + && (!outConfig.isMultiResolution()) + && (!outConfig.isDeferredConfiguration()) && (!outConfig.isShared())) { //Found valid config, return true return true; @@ -907,14 +912,6 @@ public class CameraDeviceImpl extends CameraDevice if (config.getExecutor() == null) { throw new IllegalArgumentException("Invalid executor"); } - if (mSharedMode) { - if (config.getSessionType() != SessionConfiguration.SESSION_SHARED) { - throw new IllegalArgumentException("Invalid session type"); - } - if (!checkSharedSessionConfiguration(outputConfigs)) { - throw new IllegalArgumentException("Invalid output configurations"); - } - } createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs, config.getStateCallback(), config.getExecutor(), config.getSessionType(), config.getSessionParameters()); @@ -932,17 +929,26 @@ public class CameraDeviceImpl extends CameraDevice checkIfCameraClosedOrInError(); + boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE); + if (Flags.cameraMultiClient() && mSharedMode) { + if (!isSharedSession) { + throw new IllegalArgumentException("Invalid session type"); + } + if (!checkSharedSessionConfiguration(outputConfigurations)) { + throw new IllegalArgumentException("Invalid output configurations"); + } + if (inputConfig != null) { + throw new IllegalArgumentException("Shared capture session doesn't support" + + " input configuration yet."); + } + } + boolean isConstrainedHighSpeed = (operatingMode == ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE); if (isConstrainedHighSpeed && inputConfig != null) { throw new IllegalArgumentException("Constrained high speed session doesn't support" + " input configuration yet."); } - boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE); - if (isSharedSession && inputConfig != null) { - throw new IllegalArgumentException("Shared capture session doesn't support" - + " input configuration yet."); - } if (mCurrentExtensionSession != null) { mCurrentExtensionSession.commitStats(); @@ -1004,8 +1010,7 @@ public class CameraDeviceImpl extends CameraDevice mCharacteristics); } else if (isSharedSession) { newSession = new CameraSharedCaptureSessionImpl(mNextSessionId++, - callback, executor, this, mDeviceExecutor, configureSuccess, - mIsPrimaryClient); + callback, executor, this, mDeviceExecutor, configureSuccess); } else { newSession = new CameraCaptureSessionImpl(mNextSessionId++, input, callback, executor, this, mDeviceExecutor, configureSuccess); @@ -1074,6 +1079,11 @@ public class CameraDeviceImpl extends CameraDevice synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); + if (Flags.cameraMultiClient() && mSharedMode && !mIsPrimaryClient) { + throw new UnsupportedOperationException("In shared session mode," + + "only primary clients can create capture request."); + } + for (String physicalId : physicalCameraIdSet) { if (Objects.equals(physicalId, getId())) { throw new IllegalStateException("Physical id matches the logical id!"); @@ -1100,6 +1110,11 @@ public class CameraDeviceImpl extends CameraDevice synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); + if (Flags.cameraMultiClient() && mSharedMode && !mIsPrimaryClient) { + throw new UnsupportedOperationException("In shared session mode," + + "only primary clients can create capture request."); + } + CameraMetadataNative templatedRequest = null; templatedRequest = mRemoteDevice.createDefaultRequest(templateType); @@ -1119,6 +1134,10 @@ public class CameraDeviceImpl extends CameraDevice throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); + if (Flags.cameraMultiClient() && mSharedMode) { + throw new UnsupportedOperationException("In shared session mode," + + "reprocess capture requests are not supported."); + } CameraMetadataNative resultMetadata = new CameraMetadataNative(inputResult.getNativeCopy()); @@ -1572,6 +1591,74 @@ public class CameraDeviceImpl extends CameraDevice } } + public int startStreaming(List<Surface> surfaces, CaptureCallback callback, + Executor executor) throws CameraAccessException { + // Need a valid executor, or current thread needs to have a looper, if + // callback is valid + executor = checkExecutor(executor, callback); + synchronized (mInterfaceLock) { + checkIfCameraClosedOrInError(); + for (Surface surface : surfaces) { + if (surface == null) { + throw new IllegalArgumentException("Null Surface targets are not allowed"); + } + } + // In shared session mode, if there are other active clients streaming then + // stoprepeating does not actually send request to HAL to cancel the request. + // Cameraservice will use this call to remove this client surfaces provided in its + // previous streaming request. If this is the only client for the shared camera device + // then camerservice will ask HAL to cancel the previous repeating request + stopRepeating(); + + // StartStreaming API does not allow capture parameters to be provided through a capture + // request. If the primary client has an existing repeating request, the camera service + // will either attach the provided surfaces to that request or create a default capture + // request if no repeating request is active. A default capture request is created here + // for initial use. The capture callback will provide capture results that include the + // actual capture parameters used for the streaming. + CaptureRequest.Builder builder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + for (Surface surface : surfaces) { + builder.addTarget(surface); + } + CaptureRequest request = builder.build(); + request.convertSurfaceToStreamId(mConfiguredOutputs); + + SubmitInfo requestInfo; + requestInfo = mRemoteDevice.startStreaming(request.getStreamIds(), + request.getSurfaceIds()); + request.recoverStreamIdToSurface(); + List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); + requestList.add(request); + + if (callback != null) { + mCaptureCallbackMap.put(requestInfo.getRequestId(), + new CaptureCallbackHolder( + callback, requestList, executor, true, mNextSessionId - 1)); + } else { + if (DEBUG) { + Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null"); + } + } + + if (mRepeatingRequestId != REQUEST_ID_NONE) { + checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, + requestInfo.getLastFrameNumber(), mRepeatingRequestTypes); + } + + CaptureRequest[] requestArray = requestList.toArray( + new CaptureRequest[requestList.size()]); + mRepeatingRequestId = requestInfo.getRequestId(); + mRepeatingRequestTypes = getRequestTypes(requestArray); + + if (mIdle) { + mDeviceExecutor.execute(mCallOnActive); + } + mIdle = false; + + return requestInfo.getRequestId(); + } + } + public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, Executor executor) throws CameraAccessException { List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); @@ -2894,6 +2981,11 @@ public class CameraDeviceImpl extends CameraDevice @Override public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration) throws CameraAccessException { + if (Flags.cameraMultiClient() && mSharedMode) { + throw new UnsupportedOperationException("In shared session mode," + + "extension sessions are not supported."); + } + HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>( getPhysicalIdToChars()); characteristicsMap.put(mCameraId, mCharacteristics); @@ -2929,4 +3021,4 @@ public class CameraDeviceImpl extends CameraDevice } } } -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java index a1f31c0ced5e..8c0dcfb2a28c 100644 --- a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java @@ -19,6 +19,8 @@ import android.annotation.FlaggedApi; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraOfflineSession; +import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback; import android.hardware.camera2.CameraSharedCaptureSession; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.OutputConfiguration; @@ -28,6 +30,7 @@ import android.view.Surface; import com.android.internal.camera.flags.Flags; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -46,7 +49,8 @@ public class CameraSharedCaptureSessionImpl private static final String TAG = "CameraSharedCaptureSessionImpl"; private final CameraCaptureSessionImpl mSessionImpl; private final ConditionVariable mInitialized = new ConditionVariable(); - private boolean mIsPrimary; + private final android.hardware.camera2.impl.CameraDeviceImpl mCameraDevice; + private final Executor mDeviceExecutor; /** * Create a new CameraCaptureSession. @@ -54,24 +58,32 @@ public class CameraSharedCaptureSessionImpl CameraSharedCaptureSessionImpl(int id, CameraCaptureSession.StateCallback callback, Executor stateExecutor, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, - Executor deviceStateExecutor, boolean configureSuccess, boolean isPrimary) { + Executor deviceStateExecutor, boolean configureSuccess) { CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback); mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, wrapperCallback, stateExecutor, deviceImpl, deviceStateExecutor, configureSuccess); - mIsPrimary = isPrimary; + mCameraDevice = deviceImpl; + mDeviceExecutor = deviceStateExecutor; mInitialized.open(); } @Override - public int startStreaming(List<Surface> surfaces, Executor executor, CaptureCallback listener) + public int startStreaming(List<Surface> surfaces, Executor executor, CaptureCallback callback) throws CameraAccessException { - // Todo: Need to add implementation. - return 0; + if (surfaces.isEmpty()) { + throw new IllegalArgumentException("No surfaces provided for streaming"); + } else if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } else if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + return mSessionImpl.startStreaming(surfaces, executor, callback); } @Override public void stopStreaming() throws CameraAccessException { - // Todo: Need to add implementation. + mSessionImpl.stopRepeating(); } @Override @@ -90,16 +102,24 @@ public class CameraSharedCaptureSessionImpl } @Override + public boolean supportsOfflineProcessing(Surface surface) { + return false; + } + + @Override public void abortCaptures() throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { mSessionImpl.abortCaptures(); + return; } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); } @Override public int setRepeatingRequest(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { return mSessionImpl.setRepeatingRequest(request, listener, handler); } throw new UnsupportedOperationException("Shared capture session only supports this method" @@ -107,16 +127,30 @@ public class CameraSharedCaptureSessionImpl } @Override + public int setSingleRepeatingRequest(CaptureRequest request, Executor executor, + CaptureCallback listener) + throws CameraAccessException { + if (mCameraDevice.isPrimaryClient()) { + return mSessionImpl.setSingleRepeatingRequest(request, executor, listener); + } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); + } + + @Override public void stopRepeating() throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { mSessionImpl.stopRepeating(); + return; } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); } @Override public int capture(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { return mSessionImpl.capture(request, listener, handler); } throw new UnsupportedOperationException("Shared capture session only supports this method" @@ -124,6 +158,17 @@ public class CameraSharedCaptureSessionImpl } @Override + public int captureSingleRequest(CaptureRequest request, Executor executor, + CaptureCallback listener) + throws CameraAccessException { + if (mCameraDevice.isPrimaryClient()) { + return mSessionImpl.captureSingleRequest(request, executor, listener); + } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); + } + + @Override public void tearDown(Surface surface) throws CameraAccessException { mSessionImpl.tearDown(surface); } @@ -149,48 +194,72 @@ public class CameraSharedCaptureSessionImpl } @Override + public CameraOfflineSession switchToOffline(Collection<Surface> offlineSurfaces, + Executor executor, CameraOfflineSessionCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared capture session do not support this method" + ); + } + + @Override public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback listener, Handler handler) throws CameraAccessException { - throw new UnsupportedOperationException("Shared Capture session doesn't support" + throw new UnsupportedOperationException("Shared Capture session do not support" + + " this method"); + } + + @Override + public int setRepeatingBurstRequests(List<CaptureRequest> requests, + Executor executor, CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared Capture session do not support" + " this method"); } @Override public int captureBurst(List<CaptureRequest> requests, CaptureCallback listener, Handler handler) throws CameraAccessException { - throw new UnsupportedOperationException("Shared Capture session doesn't support" + throw new UnsupportedOperationException("Shared Capture session do not support" + + " this method"); + } + + @Override + public int captureBurstRequests(List<CaptureRequest> requests, + Executor executor, CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared Capture session do not support" + " this method"); } @Override public void updateOutputConfiguration(OutputConfiguration config) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void finalizeOutputConfigurations(List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void prepare(Surface surface) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void prepare(int maxCount, Surface surface) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void closeWithoutDraining() { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index a79e084b7f41..0b8e9c2687c3 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -65,6 +65,17 @@ public class ICameraDeviceUserWrapper { } } + public SubmitInfo startStreaming(int[] streamIdxArray, int[] surfaceIdxArray) + throws CameraAccessException { + try { + return mRemoteDevice.startStreaming(streamIdxArray, surfaceIdxArray); + } catch (ServiceSpecificException e) { + throw ExceptionUtils.throwAsPublicException(e); + } catch (RemoteException e) { + throw ExceptionUtils.throwAsPublicException(e); + } + } + public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) throws CameraAccessException { try { @@ -325,4 +336,4 @@ public class ICameraDeviceUserWrapper { } } -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index e12c46322d8c..d394154a2c0e 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -1803,6 +1803,19 @@ public final class OutputConfiguration implements Parcelable { } /** + * Get the flag indicating if this {@link OutputConfiguration} is for a multi-resolution output + * with a MultiResolutionImageReader. + * + * @return true if this {@link OutputConfiguration} is for a multi-resolution output with a + * MultiResolutionImageReader. + * + * @hide + */ + public boolean isMultiResolution() { + return mIsMultiResolution; + } + + /** * Get the physical camera ID associated with this {@link OutputConfiguration}. * * <p>If this OutputConfiguration isn't targeting a physical camera of a logical diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java index cdcc92ce4404..365f870ba22d 100644 --- a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java @@ -212,7 +212,7 @@ public final class SharedSessionConfiguration { } public @Nullable String getPhysicalCameraId() { - return mPhysicalCameraId; + return mPhysicalCameraId.isEmpty() ? null : mPhysicalCameraId; } } diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java index 1e5bed519e1a..de88895ba55c 100644 --- a/core/java/android/hardware/contexthub/HubEndpoint.java +++ b/core/java/android/hardware/contexthub/HubEndpoint.java @@ -539,7 +539,10 @@ public class HubEndpoint { return this; } - /** Attach a callback interface for lifecycle events for this Endpoint */ + /** + * Attach a callback interface for lifecycle events for this Endpoint. Callback will be + * posted to the main thread. + */ @NonNull public Builder setLifecycleCallback( @NonNull HubEndpointLifecycleCallback lifecycleCallback) { @@ -560,7 +563,10 @@ public class HubEndpoint { return this; } - /** Attach a callback interface for message events for this Endpoint */ + /** + * Attach a callback interface for message events for this Endpoint. Callback will be posted + * to the main thread. + */ @NonNull public Builder setMessageCallback(@NonNull HubEndpointMessageCallback messageCallback) { mMessageCallback = messageCallback; diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 211aefffa34c..ba5dfc094afb 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -129,14 +129,38 @@ public final class DisplayTopology implements Parcelable { } /** + * Update the size of a display and normalize the topology. + * @param displayId The logical display ID + * @param width The new width + * @param height The new height + * @return True if the topology has changed. + */ + public boolean updateDisplay(int displayId, float width, float height) { + TreeNode display = findDisplay(displayId, mRoot); + if (display == null) { + return false; + } + if (floatEquals(display.mWidth, width) && floatEquals(display.mHeight, height)) { + return false; + } + display.mWidth = width; + display.mHeight = height; + normalize(); + Slog.i(TAG, "Display with ID " + displayId + " updated, new width: " + width + + ", new height: " + height); + return true; + } + + /** * Remove a display from the topology. * The default topology is created from the remaining displays, as if they were reconnected * one by one. * @param displayId The logical display ID + * @return True if the display was present in the topology and removed. */ - public void removeDisplay(int displayId) { + public boolean removeDisplay(int displayId) { if (findDisplay(displayId, mRoot) == null) { - return; + return false; } Queue<TreeNode> queue = new ArrayDeque<>(); queue.add(mRoot); @@ -159,6 +183,7 @@ public final class DisplayTopology implements Parcelable { } else { Slog.i(TAG, "Display with ID " + displayId + " removed"); } + return true; } /** @@ -685,12 +710,12 @@ public final class DisplayTopology implements Parcelable { /** * The width of the display in density-independent pixels (dp). */ - private final float mWidth; + private float mWidth; /** * The height of the display in density-independent pixels (dp). */ - private final float mHeight; + private float mHeight; /** * The position of this display relative to its parent. diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 313bad50e88e..c4d11cd8aff7 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -204,3 +204,10 @@ flag { description: "Allows the user to disable input scrolling acceleration for mouse." bug: "383555305" } + +flag { + name: "remove_fallback_modifiers" + namespace: "input" + description: "Removes modifiers from the original key event that activated the fallback, ensuring that only the intended fallback event is sent." + bug: "382545048" +} diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java index 90136ae00f6a..ffe8086ca4a1 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java @@ -93,6 +93,10 @@ import android.util.Log; * must do its own state management (keeping in mind that the service's process might be killed * by the Android System when unbound; for example, if the device is running low in memory). * + * <p> The service also provides pending intents to override the system's Quick Access activities + * via the {@link #getTargetActivityPendingIntent} and the + * {@link #getGestureTargetActivityPendingIntent} method. + * * <p> * <a name="ErrorHandling"></a> * <h3>Error handling</h3> @@ -384,6 +388,10 @@ public abstract class QuickAccessWalletService extends Service { * * <p>The pending intent will be sent when the user performs a gesture to open Wallet. * The pending intent should launch an activity. + * + * <p> If the gesture is performed and this method returns null, the system will launch the + * activity specified by the {@link #getTargetActivityPendingIntent} method. If that method + * also returns null, the system will launch the system-provided card switcher activity. */ @Nullable @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig index fb1bd1703ce6..6116d599baa0 100644 --- a/core/java/android/tracing/flags.aconfig +++ b/core/java/android/tracing/flags.aconfig @@ -70,3 +70,11 @@ flag { is_fixed_read_only: true bug: "352538294" } + +flag { + name: "system_server_large_perfetto_shmem_buffer" + namespace: "windowing_tools" + description: "Large perfetto shmem buffer" + is_fixed_read_only: true + bug: "382369925" +} diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index a8d4e2d2c70a..48dfdd4a95f4 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -16,6 +16,9 @@ package android.view; + +import static com.android.hardware.input.Flags.removeFallbackModifiers; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -458,7 +461,15 @@ public class KeyCharacterMap implements Parcelable { FallbackAction action = FallbackAction.obtain(); metaState = KeyEvent.normalizeMetaState(metaState); if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) { - action.metaState = KeyEvent.normalizeMetaState(action.metaState); + if (removeFallbackModifiers()) { + // Strip all modifiers. This is safe to do since only exact keyCode + metaState + // modifiers will trigger a fallback. + // E.g. Ctrl + Space -> language_switch (fallback generated) + // Ctrl + Alt + Space -> Ctrl + Alt + Space (no fallback generated) + action.metaState = 0; + } else { + action.metaState = KeyEvent.normalizeMetaState(action.metaState); + } return action; } action.recycle(); diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig index 20d193ea2351..f709ed7f57cd 100644 --- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig @@ -12,5 +12,5 @@ flag { name: "ccapi_baklava_enabled" namespace: "machine_learning" description: "Feature flag for baklava content capture API" - bug: "309411951" + bug: "380381249" } diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java index 11019324acd8..fcd7dfbb1769 100644 --- a/core/java/android/window/WindowTokenClientController.java +++ b/core/java/android/window/WindowTokenClientController.java @@ -148,6 +148,9 @@ public class WindowTokenClientController { info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (Exception e) { + Log.e(TAG, "Failed attachToDisplayContent", e); + return false; } if (info == null) { return false; diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 801698caff0e..a9674716eb40 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -2,16 +2,6 @@ package: "com.android.window.flags" container: "system" flag { - name: "disable_thin_letterboxing_policy" - namespace: "large_screen_experiences_app_compat" - description: "Whether reachability is disabled in case of thin letterboxing" - bug: "341027847" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "allows_screen_size_decoupled_from_status_bar_and_cutout" namespace: "large_screen_experiences_app_compat" description: "When necessary, configuration decoupled from status bar and display cutout" @@ -58,13 +48,6 @@ flag { } flag { - name: "user_min_aspect_ratio_app_default" - namespace: "large_screen_experiences_app_compat" - description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available" - bug: "310816437" -} - -flag { name: "allow_hide_scm_button" namespace: "large_screen_experiences_app_compat" description: "Whether we should allow hiding the size compat restart button" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index c97d3ec787b3..abd93cfaf179 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -105,6 +105,13 @@ flag { flag { namespace: "windowing_sdk" + name: "activity_embedding_support_for_connected_displays" + description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings." + bug: "369438353" +} + +flag { + namespace: "windowing_sdk" name: "wlinfo_oncreate" description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()" bug: "337820752" diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index 76c810bdb2c1..e91e1115ac1c 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_base.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml @@ -157,39 +157,27 @@ android:maxDrawableHeight="@dimen/notification_right_icon_size" /> - <LinearLayout - android:id="@+id/notification_buttons_column" + <FrameLayout + android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_alignParentEnd="true" - android:orientation="vertical" + android:minWidth="@dimen/notification_content_margin_end" > - <include layout="@layout/notification_close_button" - android:layout_width="@dimen/notification_close_button_size" - android:layout_height="@dimen/notification_close_button_size" - android:layout_gravity="end" - android:layout_marginEnd="20dp" - /> - - <FrameLayout - android:id="@+id/expand_button_touch_container" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_weight="1" - android:minWidth="@dimen/notification_content_margin_end" - > - - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical|end" - /> - - </FrameLayout> + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> - </LinearLayout> + </FrameLayout> </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml index 2e0a7afc3cd1..2d367337bb6f 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_media.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml @@ -194,4 +194,11 @@ </FrameLayout> </LinearLayout> + + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </com.android.internal.widget.MediaNotificationView> diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml index f644adefda9d..fbecb8c30b9c 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -199,6 +199,12 @@ </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </com.android.internal.widget.NotificationMaxHeightFrameLayout> <LinearLayout diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml index fc727e1c72f5..2d30d8a8bbb6 100644 --- a/core/res/res/layout/notification_2025_template_header.xml +++ b/core/res/res/layout/notification_2025_template_header.xml @@ -60,7 +60,7 @@ android:layout_height="match_parent" android:layout_alignParentStart="true" android:layout_centerVertical="true" - android:layout_toStartOf="@id/notification_buttons_column" + android:layout_toStartOf="@id/expand_button" android:layout_alignWithParentIfMissing="true" android:clipChildren="false" android:gravity="center_vertical" @@ -81,28 +81,17 @@ android:focusable="false" /> - <LinearLayout - android:id="@+id/notification_buttons_column" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:orientation="vertical" - > - - <include layout="@layout/notification_close_button" - android:layout_width="@dimen/notification_close_button_size" - android:layout_height="@dimen/notification_close_button_size" - android:layout_gravity="end" - android:layout_marginEnd="20dp" - /> - - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - /> + android:layout_centerVertical="true" + android:layout_alignParentEnd="true" /> - </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_alignParentTop="true" + android:layout_alignParentEnd="true" /> </NotificationHeaderView> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 565e28e0cc87..45a5d85a097d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2165,6 +2165,17 @@ config_enableGeofenceOverlay is false. --> <string name="config_geofenceProviderPackageName" translatable="false">@null</string> + <!-- Whether to enable GNSS assistance overlay which allows GnssAssistanceProvider to be + replaced by an app at run-time. When disabled, only the + config_gnssAssistanceProviderPackageName package will be searched for + GnssAssistanceProvider, otherwise any system package is eligible. Anyone who wants to + disable the overlay mechanism can set it to false. + --> + <bool name="config_enableGnssAssistanceOverlay" translatable="false">true</bool> + <!-- Package name providing GNSS assistance API support. Used only when + config_enableGnssAssistanceOverlay is false. --> + <string name="config_gnssAssistanceProviderPackageName" translatable="false">@null</string> + <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware Activity-Recognition to be replaced by an app at run-time. When disabled, only the config_activityRecognitionHardwarePackageName package will be searched for diff --git a/core/res/res/values/config_watch.xml b/core/res/res/values/config_watch.xml index 629a343f1280..bcb1e0941b5a 100644 --- a/core/res/res/values/config_watch.xml +++ b/core/res/res/values/config_watch.xml @@ -15,8 +15,7 @@ --> <resources> - <!-- TODO(b/382103556): use predefined Material3 token --> <!-- For Wear Material3 --> - <dimen name="config_wearMaterial3_buttonCornerRadius">26dp</dimen> - <dimen name="config_wearMaterial3_bottomDialogCornerRadius">18dp</dimen> + <dimen name="config_wearMaterial3_buttonCornerRadius">@dimen/config_shapeCornerRadiusLarge</dimen> + <dimen name="config_wearMaterial3_bottomDialogCornerRadius">@dimen/config_shapeCornerRadiusMedium</dimen> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 73e06f6a2520..8195d38993c8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2040,6 +2040,7 @@ <java-symbol type="bool" name="config_useGnssHardwareProvider" /> <java-symbol type="bool" name="config_enableGeocoderOverlay" /> <java-symbol type="bool" name="config_enableGeofenceOverlay" /> + <java-symbol type="bool" name="config_enableGnssAssistanceOverlay" /> <java-symbol type="bool" name="config_enableNetworkLocationOverlay" /> <java-symbol type="bool" name="config_sf_limitedAlpha" /> <java-symbol type="bool" name="config_unplugTurnsOnScreen" /> @@ -2223,6 +2224,7 @@ <java-symbol type="string" name="config_gnssLocationProviderPackageName" /> <java-symbol type="string" name="config_geocoderProviderPackageName" /> <java-symbol type="string" name="config_geofenceProviderPackageName" /> + <java-symbol type="string" name="config_gnssAssistanceProviderPackageName" /> <java-symbol type="string" name="config_networkLocationProviderPackageName" /> <java-symbol type="string" name="config_wimaxManagerClassname" /> <java-symbol type="string" name="config_wimaxNativeLibLocation" /> diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt index 4a227d8ff1ef..255d09b854bd 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt @@ -86,7 +86,7 @@ class DisplayTopologyTest { verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1) val display2 = display1.children[0] - verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP, + verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP, offset = width1 / 2 - width2 / 2, noOfChildren = 1) var display = display2 @@ -99,6 +99,76 @@ class DisplayTopologyTest { } @Test + fun updateDisplay() { + val displayId = 1 + val width = 800f + val height = 600f + + val newWidth = 1000f + val newHeight = 500f + topology.addDisplay(displayId, width, height) + assertThat(topology.updateDisplay(displayId, newWidth, newHeight)).isTrue() + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + verifyDisplay(topology.root!!, displayId, newWidth, newHeight, noOfChildren = 0) + } + + @Test + fun updateDisplay_notUpdated() { + val displayId = 1 + val width = 800f + val height = 600f + topology.addDisplay(displayId, width, height) + + // Same size + assertThat(topology.updateDisplay(displayId, width, height)).isFalse() + + // Display doesn't exist + assertThat(topology.updateDisplay(/* displayId= */ 100, width, height)).isFalse() + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) + } + + @Test + fun updateDisplayDoesNotAffectDefaultTopology() { + val width1 = 700f + val height = 600f + topology.addDisplay(/* displayId= */ 1, width1, height) + + val width2 = 800f + val noOfDisplays = 30 + for (i in 2..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width2, height) + } + + val displaysToUpdate = arrayOf(3, 7, 18) + val newWidth = 1000f + val newHeight = 1500f + for (i in displaysToUpdate) { + assertThat(topology.updateDisplay(/* displayId= */ i, newWidth, newHeight)).isTrue() + } + + assertThat(topology.primaryDisplayId).isEqualTo(1) + + val display1 = topology.root!! + verifyDisplay(display1, id = 1, width1, height, noOfChildren = 1) + + val display2 = display1.children[0] + verifyDisplay(display2, id = 2, width2, height, POSITION_TOP, + offset = width1 / 2 - width2 / 2, noOfChildren = 1) + + var display = display2 + for (i in 3..noOfDisplays) { + display = display.children[0] + // The last display should have no children + verifyDisplay(display, id = i, if (i in displaysToUpdate) newWidth else width2, + if (i in displaysToUpdate) newHeight else height, POSITION_RIGHT, offset = 0f, + noOfChildren = if (i < noOfDisplays) 1 else 0) + } + } + + @Test fun removeDisplays() { val displayId1 = 1 val width1 = 800f @@ -117,7 +187,7 @@ class DisplayTopologyTest { } var removedDisplays = arrayOf(20) - topology.removeDisplay(20) + assertThat(topology.removeDisplay(20)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) @@ -139,11 +209,11 @@ class DisplayTopologyTest { noOfChildren = if (i < noOfDisplays) 1 else 0) } - topology.removeDisplay(22) + assertThat(topology.removeDisplay(22)).isTrue() removedDisplays += 22 - topology.removeDisplay(23) + assertThat(topology.removeDisplay(23)).isTrue() removedDisplays += 23 - topology.removeDisplay(25) + assertThat(topology.removeDisplay(25)).isTrue() removedDisplays += 25 assertThat(topology.primaryDisplayId).isEqualTo(displayId1) @@ -174,7 +244,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) - topology.removeDisplay(displayId) + assertThat(topology.removeDisplay(displayId)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) assertThat(topology.root).isNull() @@ -187,7 +257,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) - topology.removeDisplay(3) + assertThat(topology.removeDisplay(3)).isFalse() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) @@ -203,7 +273,7 @@ class DisplayTopologyTest { topology = DisplayTopology(/* root= */ null, displayId2) topology.addDisplay(displayId1, width, height) topology.addDisplay(displayId2, width, height) - topology.removeDisplay(displayId2) + assertThat(topology.removeDisplay(displayId2)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0) diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp index 41d1b5c15369..eecf199a3ec2 100644 --- a/libs/WindowManager/Shell/multivalentTests/Android.bp +++ b/libs/WindowManager/Shell/multivalentTests/Android.bp @@ -55,6 +55,7 @@ android_robolectric_test { "truth", "flag-junit-base", "flag-junit", + "testables", ], auto_gen_config: true, } @@ -77,6 +78,7 @@ android_test { "truth", "platform-test-annotations", "platform-test-rules", + "testables", ], libs: [ "android.test.base.stubs.system", diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt index 0d8f80935f5a..3e01256fd67c 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles.bar +import android.animation.AnimatorTestRule +import android.app.ActivityManager import android.content.Context import android.graphics.Insets import android.graphics.Rect @@ -23,7 +25,6 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -36,27 +37,34 @@ import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger import com.android.wm.shell.bubbles.BubbleOverflow import com.android.wm.shell.bubbles.BubblePositioner +import com.android.wm.shell.bubbles.BubbleTaskView import com.android.wm.shell.bubbles.DeviceConfig import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.FakeBubbleFactory -import com.android.wm.shell.bubbles.FakeBubbleTaskViewFactory +import com.android.wm.shell.taskview.TaskView +import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import org.junit.After import org.junit.Before -import org.junit.ClassRule +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever /** Tests for [BubbleBarAnimationHelper] */ @SmallTest @RunWith(AndroidJUnit4::class) class BubbleBarAnimationHelperTest { - companion object { - @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) + companion object { const val SCREEN_WIDTH = 2000 const val SCREEN_HEIGHT = 1000 } @@ -148,6 +156,26 @@ class BubbleBarAnimationHelperTest { } @Test + fun animateSwitch_bubbleToBubble_updateTaskBounds() { + val fromBubble = createBubble("from").initialize(container) + val toBubbleTaskController = mock<TaskViewTaskController>() + val toBubble = createBubble("to", toBubbleTaskController).initialize(container) + + getInstrumentation().runOnMainSync { + animationHelper.animateSwitch(fromBubble, toBubble) {} + // Start the animation, but don't finish + animatorTestRule.advanceTimeBy(100) + } + getInstrumentation().waitForIdleSync() + // Clear invocations to ensure that bounds update happens after animation ends + clearInvocations(toBubbleTaskController) + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) } + getInstrumentation().waitForIdleSync() + + verify(toBubbleTaskController).setWindowBounds(any()) + } + + @Test fun animateSwitch_bubbleToOverflow_oldHiddenNewShown() { val fromBubble = createBubble(key = "from").initialize(container) val overflow = createOverflow().initialize(container) @@ -193,13 +221,43 @@ class BubbleBarAnimationHelperTest { assertThat(toBubble.bubbleBarExpandedView?.isSurfaceZOrderedOnTop).isFalse() } - private fun createBubble(key: String): Bubble { + @Test + fun animateToRestPosition_updateTaskBounds() { + val taskController = mock<TaskViewTaskController>() + val bubble = createBubble("key", taskController).initialize(container) + + getInstrumentation().runOnMainSync { + animationHelper.animateExpansion(bubble) {} + animatorTestRule.advanceTimeBy(1000) + } + getInstrumentation().waitForIdleSync() + getInstrumentation().runOnMainSync { + animationHelper.animateToRestPosition() + animatorTestRule.advanceTimeBy(100) + } + // Clear invocations to ensure that bounds update happens after animation ends + clearInvocations(taskController) + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) } + getInstrumentation().waitForIdleSync() + + verify(taskController).setWindowBounds(any()) + } + + private fun createBubble( + key: String, + taskViewTaskController: TaskViewTaskController = mock<TaskViewTaskController>(), + ): Bubble { + val taskView = TaskView(context, taskViewTaskController) + val taskInfo = mock<ActivityManager.RunningTaskInfo>() + whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) + val bubbleTaskView = BubbleTaskView(taskView, mainExecutor) + val bubbleBarExpandedView = FakeBubbleFactory.createExpandedView( context, bubblePositioner, expandedViewManager, - FakeBubbleTaskViewFactory(context, mainExecutor).create(), + bubbleTaskView, mainExecutor, bgExecutor, bubbleLogger, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 3e8a9b64dac6..3188e5b9c6d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -463,6 +463,7 @@ public class BubbleBarAnimationHelper { super.onAnimationEnd(animation); bbev.resetPivot(); bbev.setDragging(false); + updateExpandedView(bbev); } }); startNewAnimator(animatorSet); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index c74bf53268f9..9ebb7f5aa270 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -643,7 +643,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged t.setPosition(animatingLeash, x, endY); t.setAlpha(animatingLeash, 1.f); } - dispatchEndPositioning(mDisplayId, mCancelled, t); + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + dispatchEndPositioning(mDisplayId, mCancelled, t); + } if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_ANIMATION_RUNNING); @@ -659,6 +661,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_ANIMATION_RUNNING); } + if (android.view.inputmethod.Flags.refactorInsetsController()) { + // In split screen, we also set {@link + // WindowContainer#mExcludeInsetsTypes} but this should only happen after + // the IME client visibility was set. Otherwise the insets will we + // dispatched too early, and we get a flicker. Thus, only dispatching it + // after reporting that the IME is hidden to system server. + dispatchEndPositioning(mDisplayId, mCancelled, t); + } if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END, mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index 606a729305b4..90191345147c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -82,7 +82,7 @@ fun calculateInitialBounds( // For portrait resizeable activities, respect apps fullscreen width but // apply ideal size height. Size( - taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth, + taskInfo.appCompatTaskInfo.topActivityAppBounds.width(), idealSize.height, ) } else { @@ -104,7 +104,7 @@ fun calculateInitialBounds( // apply custom app width. Size( customPortraitWidthForLandscapeApp, - taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight, + taskInfo.appCompatTaskInfo.topActivityAppBounds.height(), ) } else { // For portrait resizeable activities, simply apply ideal size. @@ -196,13 +196,8 @@ fun maximizeSizeGivenAspectRatio( /** Calculates the aspect ratio of an activity from its fullscreen bounds. */ fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { - val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth - val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight - if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed || !taskInfo.canChangeAspectRatio) { - return maxOf(appLetterboxWidth, appLetterboxHeight) / - minOf(appLetterboxWidth, appLetterboxHeight).toFloat() - } - val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f + if (taskInfo.appCompatTaskInfo.topActivityAppBounds.isEmpty) return 1f + val appBounds = taskInfo.appCompatTaskInfo.topActivityAppBounds return maxOf(appBounds.height(), appBounds.width()) / minOf(appBounds.height(), appBounds.width()).toFloat() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 4f37180baa37..e1c2153014fa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -4160,8 +4160,7 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation = SCREEN_ORIENTATION_LANDSCAPE configuration.windowConfiguration.appBounds = bounds } - appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width() - appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height() + appCompatTaskInfo.topActivityAppBounds.set(0, 0, bounds.width(), bounds.height()) isResizeable = false } @@ -4879,15 +4878,19 @@ class DesktopTasksControllerTest : ShellTestCase() { appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride if (deviceOrientation == ORIENTATION_LANDSCAPE) { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT + appCompatTaskInfo.topActivityAppBounds.set( + 0, + 0, + DISPLAY_DIMENSION_LONG, + DISPLAY_DIMENSION_SHORT, + ) } else { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG + appCompatTaskInfo.topActivityAppBounds.set( + 0, + 0, + DISPLAY_DIMENSION_SHORT, + DISPLAY_DIMENSION_LONG, + ) } if (shouldLetterbox) { @@ -4897,17 +4900,15 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation == SCREEN_ORIENTATION_PORTRAIT ) { // Letterbox to portrait size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1200 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1600 + appCompatTaskInfo.isTopActivityLetterboxed = true + appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1200, 1600) } else if ( deviceOrientation == ORIENTATION_PORTRAIT && screenOrientation == SCREEN_ORIENTATION_LANDSCAPE ) { // Letterbox to landscape size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1600 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1200 + appCompatTaskInfo.isTopActivityLetterboxed = true + appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1600, 1200) } } } diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 1bc15d72bacc..cc4a29b31996 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -199,6 +199,7 @@ cc_test { // This is to suppress warnings/errors from gtest "-Wno-unnamed-type-template-args", ], + require_root: true, srcs: [ // Helpers/infra for testing. "tests/CommonHelpers.cpp", diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp index 70d14a11830e..970463492c1a 100644 --- a/libs/androidfw/TypeWrappers.cpp +++ b/libs/androidfw/TypeWrappers.cpp @@ -16,8 +16,6 @@ #include <androidfw/TypeWrappers.h> -#include <algorithm> - namespace android { TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(data->entryCount)) { @@ -31,30 +29,44 @@ TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl( ALOGE("Type's entry indices extend beyond its boundaries"); mLength = 0; } else { - mLength = ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx + 1; + mLength = dtohs(ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx) + 1; } } } TypeVariant::iterator& TypeVariant::iterator::operator++() { - mIndex++; + ++mIndex; if (mIndex > mTypeVariant->mLength) { mIndex = mTypeVariant->mLength; } - return *this; -} -static bool keyCompare(uint32_t entry, uint16_t index) { - return dtohs(ResTable_sparseTypeEntry{entry}.idx) < index; + const ResTable_type* type = mTypeVariant->data; + if ((type->flags & ResTable_type::FLAG_SPARSE) == 0) { + return *this; + } + + // Need to adjust |mSparseIndex| as well if we've passed its current element. + const uint32_t entryCount = dtohl(type->entryCount); + const auto entryIndices = reinterpret_cast<const uint32_t*>( + reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize)); + if (mSparseIndex >= entryCount) { + return *this; // done + } + const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex); + if (mIndex > dtohs(element->idx)) { + ++mSparseIndex; + } + + return *this; } const ResTable_entry* TypeVariant::iterator::operator*() const { - const ResTable_type* type = mTypeVariant->data; if (mIndex >= mTypeVariant->mLength) { - return NULL; + return nullptr; } - const uint32_t entryCount = dtohl(mTypeVariant->data->entryCount); + const ResTable_type* type = mTypeVariant->data; + const uint32_t entryCount = dtohl(type->entryCount); const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type) + dtohl(type->header.size); const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>( @@ -63,18 +75,19 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { sizeof(uint16_t) : sizeof(uint32_t); if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) { ALOGE("Type's entry indices extend beyond its boundaries"); - return NULL; + return nullptr; } uint32_t entryOffset; if (type->flags & ResTable_type::FLAG_SPARSE) { - auto iter = std::lower_bound(entryIndices, entryIndices + entryCount, mIndex, keyCompare); - if (iter == entryIndices + entryCount - || dtohs(ResTable_sparseTypeEntry{*iter}.idx) != mIndex) { - return NULL; + if (mSparseIndex >= entryCount) { + return nullptr; } - - entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u; + const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex); + if (dtohs(element->idx) != mIndex) { + return nullptr; + } + entryOffset = static_cast<uint32_t>(dtohs(element->offset)) * 4u; } else if (type->flags & ResTable_type::FLAG_OFFSET16) { auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices); entryOffset = offset_from16(entryIndices16[mIndex]); @@ -83,25 +96,25 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { } if (entryOffset == ResTable_type::NO_ENTRY) { - return NULL; + return nullptr; } if ((entryOffset & 0x3) != 0) { ALOGE("Index %u points to entry with unaligned offset 0x%08x", mIndex, entryOffset); - return NULL; + return nullptr; } const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( reinterpret_cast<uintptr_t>(type) + dtohl(type->entriesStart) + entryOffset); if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) { ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex); - return NULL; + return nullptr; } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) { ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex); - return NULL; + return nullptr; } else if (entry->size() < sizeof(*entry)) { ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size()); - return NULL; + return nullptr; } return entry; } diff --git a/libs/androidfw/include/androidfw/TypeWrappers.h b/libs/androidfw/include/androidfw/TypeWrappers.h index fb2fad619011..db641b78a4e4 100644 --- a/libs/androidfw/include/androidfw/TypeWrappers.h +++ b/libs/androidfw/include/androidfw/TypeWrappers.h @@ -27,24 +27,14 @@ struct TypeVariant { class iterator { public: - iterator& operator=(const iterator& rhs) { - mTypeVariant = rhs.mTypeVariant; - mIndex = rhs.mIndex; - return *this; - } - bool operator==(const iterator& rhs) const { return mTypeVariant == rhs.mTypeVariant && mIndex == rhs.mIndex; } - bool operator!=(const iterator& rhs) const { - return mTypeVariant != rhs.mTypeVariant || mIndex != rhs.mIndex; - } - iterator operator++(int) { - uint32_t prevIndex = mIndex; + iterator prev = *this; operator++(); - return iterator(mTypeVariant, prevIndex); + return prev; } const ResTable_entry* operator->() const { @@ -60,18 +50,26 @@ struct TypeVariant { private: friend struct TypeVariant; - iterator(const TypeVariant* tv, uint32_t index) - : mTypeVariant(tv), mIndex(index) {} + + enum class Kind { Begin, End }; + iterator(const TypeVariant* tv, Kind kind) + : mTypeVariant(tv) { + mSparseIndex = mIndex = kind == Kind::Begin ? 0 : tv->mLength; + // mSparseIndex here is technically past the number of sparse entries, but it is still + // ok as it is enough to infer that this is the end iterator. + } + const TypeVariant* mTypeVariant; uint32_t mIndex; + uint32_t mSparseIndex; }; iterator beginEntries() const { - return iterator(this, 0); + return iterator(this, iterator::Kind::Begin); } iterator endEntries() const { - return iterator(this, mLength); + return iterator(this, iterator::Kind::End); } const ResTable_type* data; diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp index ed30904ec179..d66e05805484 100644 --- a/libs/androidfw/tests/TypeWrappers_test.cpp +++ b/libs/androidfw/tests/TypeWrappers_test.cpp @@ -14,28 +14,42 @@ * limitations under the License. */ -#include <algorithm> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> -#include <utils/String8.h> +#include <androidfw/Util.h> + +#include <optional> +#include <vector> #include <gtest/gtest.h> namespace android { -// create a ResTable_type in memory with a vector of Res_value* -static ResTable_type* createTypeTable(std::vector<Res_value*>& values, - bool compact_entry = false, - bool short_offsets = false) +using ResValueVector = std::vector<std::optional<Res_value>>; + +// create a ResTable_type in memory +static util::unique_cptr<ResTable_type> createTypeTable( + const ResValueVector& in_values, bool compact_entry, bool short_offsets, bool sparse) { + ResValueVector sparse_values; + if (sparse) { + std::ranges::copy_if(in_values, std::back_inserter(sparse_values), + [](auto&& v) { return v.has_value(); }); + } + const ResValueVector& values = sparse ? sparse_values : in_values; + ResTable_type t{}; t.header.type = RES_TABLE_TYPE_TYPE; t.header.headerSize = sizeof(t); t.header.size = sizeof(t); t.id = 1; - t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0; + t.flags = sparse + ? ResTable_type::FLAG_SPARSE + : short_offsets ? ResTable_type::FLAG_OFFSET16 : 0; - t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t)); + t.header.size += values.size() * + (sparse ? sizeof(ResTable_sparseTypeEntry) : + short_offsets ? sizeof(uint16_t) : sizeof(uint32_t)); t.entriesStart = t.header.size; t.entryCount = values.size(); @@ -53,9 +67,18 @@ static ResTable_type* createTypeTable(std::vector<Res_value*>& values, memcpy(p_header, &t, sizeof(t)); size_t i = 0, entry_offset = 0; - uint32_t k = 0; - for (auto const& v : values) { - if (short_offsets) { + uint32_t sparse_index = 0; + + for (auto const& v : in_values) { + if (sparse) { + if (!v) { + ++i; + continue; + } + const auto p = reinterpret_cast<ResTable_sparseTypeEntry*>(p_offsets) + sparse_index++; + p->idx = i; + p->offset = (entry_offset >> 2) & 0xffffu; + } else if (short_offsets) { uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i; *p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu; } else { @@ -83,62 +106,92 @@ static ResTable_type* createTypeTable(std::vector<Res_value*>& values, } i++; } - return reinterpret_cast<ResTable_type*>(data); + return util::unique_cptr<ResTable_type>{reinterpret_cast<ResTable_type*>(data)}; } TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) { - std::vector<Res_value *> values; - - Res_value *v1 = new Res_value{}; - values.push_back(v1); - - values.push_back(nullptr); - - Res_value *v2 = new Res_value{}; - values.push_back(v2); - - Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678}; - values.push_back(v3); + ResValueVector values; + + values.push_back(std::nullopt); + values.push_back(Res_value{}); + values.push_back(std::nullopt); + values.push_back(Res_value{}); + values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678}); + values.push_back(std::nullopt); + values.push_back(std::nullopt); + values.push_back(std::nullopt); + values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x87654321}); // test for combinations of compact_entry and short_offsets - for (size_t i = 0; i < 4; i++) { - bool compact_entry = i & 0x1, short_offsets = i & 0x2; - ResTable_type* data = createTypeTable(values, compact_entry, short_offsets); - TypeVariant v(data); + for (size_t i = 0; i < 8; i++) { + bool compact_entry = i & 0x1, short_offsets = i & 0x2, sparse = i & 0x4; + auto data = createTypeTable(values, compact_entry, short_offsets, sparse); + TypeVariant v(data.get()); TypeVariant::iterator iter = v.beginEntries(); ASSERT_EQ(uint32_t(0), iter.index()); - ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(0), iter->key()); + ASSERT_TRUE(NULL == *iter); ASSERT_NE(v.endEntries(), iter); - iter++; + ++iter; ASSERT_EQ(uint32_t(1), iter.index()); - ASSERT_TRUE(NULL == *iter); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(1), iter->key()); ASSERT_NE(v.endEntries(), iter); iter++; ASSERT_EQ(uint32_t(2), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + + ++iter; + + ASSERT_EQ(uint32_t(3), iter.index()); ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(2), iter->key()); + ASSERT_EQ(uint32_t(3), iter->key()); ASSERT_NE(v.endEntries(), iter); iter++; - ASSERT_EQ(uint32_t(3), iter.index()); + ASSERT_EQ(uint32_t(4), iter.index()); ASSERT_TRUE(NULL != *iter); ASSERT_EQ(iter->is_compact(), compact_entry); - ASSERT_EQ(uint32_t(3), iter->key()); + ASSERT_EQ(uint32_t(4), iter->key()); ASSERT_EQ(uint32_t(0x12345678), iter->value().data); ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType); + ++iter; + + ASSERT_EQ(uint32_t(5), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + + ++iter; + + ASSERT_EQ(uint32_t(6), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + + ++iter; + + ASSERT_EQ(uint32_t(7), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + iter++; - ASSERT_EQ(v.endEntries(), iter); + ASSERT_EQ(uint32_t(8), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(iter->is_compact(), compact_entry); + ASSERT_EQ(uint32_t(8), iter->key()); + ASSERT_EQ(uint32_t(0x87654321), iter->value().data); + ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType); - free(data); + ++iter; + + ASSERT_EQ(v.endEntries(), iter); } } diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp index 8af4b7e8f4c8..4fecf4de0312 100644 --- a/libs/protoutil/Android.bp +++ b/libs/protoutil/Android.bp @@ -59,7 +59,6 @@ cc_library { apex_available: [ "//apex_available:platform", "com.android.os.statsd", - "test_com.android.os.statsd", "com.android.uprobestats", ], } diff --git a/location/api/system-current.txt b/location/api/system-current.txt index 0c2f3adc2838..023bad26e4f5 100644 --- a/location/api/system-current.txt +++ b/location/api/system-current.txt @@ -1459,6 +1459,13 @@ package android.location.provider { field public static final String ACTION_GEOCODE_PROVIDER = "com.android.location.service.GeocodeProvider"; } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public abstract class GnssAssistanceProviderBase { + ctor public GnssAssistanceProviderBase(@NonNull android.content.Context, @NonNull String); + method @NonNull public final android.os.IBinder getBinder(); + method public abstract void onRequest(@NonNull android.os.OutcomeReceiver<android.location.GnssAssistance,java.lang.Throwable>); + field public static final String ACTION_GNSS_ASSISTANCE_PROVIDER = "android.location.provider.action.GNSS_ASSISTANCE_PROVIDER"; + } + public abstract class LocationProviderBase { ctor public LocationProviderBase(@NonNull android.content.Context, @NonNull String, @NonNull android.location.provider.ProviderProperties); method @Nullable public final android.os.IBinder getBinder(); diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index c02cc808d60c..1b38982f48c1 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -167,4 +167,4 @@ flag { namespace: "location" description: "Flag for GNSS assistance interface" bug: "209078566" -}
\ No newline at end of file +} diff --git a/location/java/android/location/provider/GnssAssistanceProviderBase.java b/location/java/android/location/provider/GnssAssistanceProviderBase.java new file mode 100644 index 000000000000..f4b26d5033a5 --- /dev/null +++ b/location/java/android/location/provider/GnssAssistanceProviderBase.java @@ -0,0 +1,152 @@ +/* + * 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.location.provider; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.content.Intent; +import android.location.GnssAssistance; +import android.location.flags.Flags; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.OutcomeReceiver; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + + +/** + * Base class for GNSS assistance providers outside the system server. + * + * <p>GNSS assistance providers should be wrapped in a non-exported service which returns the result + * of {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The + * service should not be exported so that components other than the system server cannot bind to it. + * Alternatively, the service may be guarded by a permission that only system server can obtain. The + * service may specify metadata on its capabilities: + * + * <ul> + * <li>"serviceVersion": An integer version code to help tie break if multiple services are + * capable of implementing the geocode provider. All else equal, the service with the highest + * version code will be chosen. Assumed to be 0 if not specified. + * <li>"serviceIsMultiuser": A boolean property, indicating if the service wishes to take + * responsibility for handling changes to the current user on the device. If true, the service + * will always be bound from the system user. If false, the service will always be bound from + * the current user. If the current user changes, the old binding will be released, and a new + * binding established under the new user. Assumed to be false if not specified. + * </ul> + * + * <p>The service should have an intent filter in place for the GNSS assistance provider as + * specified by the constant in this class. + * + * <p>GNSS assistance providers are identified by their UID / package name / attribution tag. Based + * on this identity, geocode providers may be given some special privileges. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public abstract class GnssAssistanceProviderBase { + + /** + * The action the wrapping service should have in its intent filter to implement the GNSS + * Assistance provider. + */ + public static final String ACTION_GNSS_ASSISTANCE_PROVIDER = + "android.location.provider.action.GNSS_ASSISTANCE_PROVIDER"; + + final String mTag; + @Nullable + final String mAttributionTag; + final IBinder mBinder; + + /** + * Subclasses should pass in a context and an arbitrary tag that may be used for logcat logging + * of errors, and thus should uniquely identify the class. + */ + public GnssAssistanceProviderBase(@NonNull Context context, @NonNull String tag) { + mTag = tag; + mAttributionTag = context.getAttributionTag(); + mBinder = new GnssAssistanceProviderBase.Service(); + } + + /** + * Returns the IBinder instance that should be returned from the {@link + * android.app.Service#onBind(Intent)} method of the wrapping service. + */ + @NonNull + public final IBinder getBinder() { + return mBinder; + } + + /** + * Requests GNSS assistance data of the given arguments. The given callback must be invoked + * once. + */ + public abstract void onRequest( + @NonNull OutcomeReceiver<GnssAssistance, Throwable> callback); + + private class Service extends IGnssAssistanceProvider.Stub { + @Override + public void request(IGnssAssistanceCallback callback) { + try { + onRequest(new GnssAssistanceProviderBase.SingleUseCallback(callback)); + } catch (RuntimeException e) { + // exceptions on one-way binder threads are dropped - move to a different thread + Log.w(mTag, e); + new Handler(Looper.getMainLooper()) + .post( + () -> { + throw new AssertionError(e); + }); + } + } + } + + private static class SingleUseCallback implements + OutcomeReceiver<GnssAssistance, Throwable> { + + private final AtomicReference<IGnssAssistanceCallback> mCallback; + + SingleUseCallback(IGnssAssistanceCallback callback) { + mCallback = new AtomicReference<>(callback); + } + + @Override + public void onError(Throwable e) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onError(); + } catch (RemoteException r) { + throw r.rethrowFromSystemServer(); + } + } + + @Override + public void onResult(GnssAssistance result) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onResult(result); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/location/java/android/location/provider/IGnssAssistanceCallback.aidl b/location/java/android/location/provider/IGnssAssistanceCallback.aidl new file mode 100644 index 000000000000..ea38d08df6c2 --- /dev/null +++ b/location/java/android/location/provider/IGnssAssistanceCallback.aidl @@ -0,0 +1,28 @@ +/* + * 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.location.provider; + +import android.location.GnssAssistance; + +/** + * Binder interface for GNSS assistance callbacks. + * @hide + */ +oneway interface IGnssAssistanceCallback { + void onError(); + void onResult(in GnssAssistance result); +} diff --git a/location/java/android/location/provider/IGnssAssistanceProvider.aidl b/location/java/android/location/provider/IGnssAssistanceProvider.aidl new file mode 100644 index 000000000000..1796e9edb347 --- /dev/null +++ b/location/java/android/location/provider/IGnssAssistanceProvider.aidl @@ -0,0 +1,28 @@ +/* + * 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.location.provider; + +import android.location.provider.IGnssAssistanceCallback; + +/** + * Binder interface for services that implement GNSS assistance providers. Do not implement this + * directly, extend {@link GnssAssistanceProviderBase} instead. + * @hide + */ +oneway interface IGnssAssistanceProvider { + void request(in IGnssAssistanceCallback callback); +} diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 60584d9c6f72..f42017dc835a 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -226,6 +226,10 @@ public abstract class MediaRoute2ProviderService extends Service { @GuardedBy("mSessionLock") private final ArrayMap<String, MediaStreams> mOngoingMediaStreams = new ArrayMap<>(); + @GuardedBy("mSessionLock") + private final ArrayMap<String, RoutingSessionInfo> mPendingSystemSessionReleases = + new ArrayMap<>(); + public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); } @@ -419,7 +423,7 @@ public abstract class MediaRoute2ProviderService extends Service { } AudioFormat audioFormat = formats.mAudioFormat; - var mediaStreamsBuilder = new MediaStreams.Builder(); + var mediaStreamsBuilder = new MediaStreams.Builder(sessionInfo); if (audioFormat != null) { populateAudioStream(audioFormat, uid, mediaStreamsBuilder); } @@ -526,8 +530,14 @@ public abstract class MediaRoute2ProviderService extends Service { RoutingSessionInfo sessionInfo; synchronized (mSessionLock) { sessionInfo = mSessionInfos.remove(sessionId); - maybeReleaseMediaStreams(sessionId); - + if (Flags.enableMirroringInMediaRouter2()) { + if (sessionInfo == null) { + sessionInfo = maybeReleaseMediaStreams(sessionId); + } + if (sessionInfo == null) { + sessionInfo = mPendingSystemSessionReleases.remove(sessionId); + } + } if (sessionInfo == null) { Log.w(TAG, "notifySessionReleased: Ignoring unknown session info."); return; @@ -544,20 +554,26 @@ public abstract class MediaRoute2ProviderService extends Service { } } - /** Releases any system media routing resources associated with the given {@code sessionId}. */ - private boolean maybeReleaseMediaStreams(String sessionId) { + /** + * Releases any system media routing resources associated with the given {@code sessionId}. + * + * @return The {@link RoutingSessionInfo} that corresponds to the released media streams, or + * null if no streams were released. + */ + @Nullable + private RoutingSessionInfo maybeReleaseMediaStreams(String sessionId) { if (!Flags.enableMirroringInMediaRouter2()) { - return false; + return null; } synchronized (mSessionLock) { var streams = mOngoingMediaStreams.remove(sessionId); if (streams != null) { releaseAudioStream(streams.mAudioPolicy, streams.mAudioRecord); // TODO: b/380431086: Release the video stream once implemented. - return true; + return streams.mSessionInfo; } } - return false; + return null; } // We cannot reach the code that requires MODIFY_AUDIO_ROUTING without holding it. @@ -1026,11 +1042,15 @@ public abstract class MediaRoute2ProviderService extends Service { if (!checkCallerIsSystem()) { return; } - // We proactively release the system media routing once the system requests it, to - // ensure it happens immediately. - if (!maybeReleaseMediaStreams(sessionId) - && !checkSessionIdIsValid(sessionId, "releaseSession")) { - return; + synchronized (mSessionLock) { + // We proactively release the system media routing session resources when the + // system requests it, to ensure it happens immediately. + RoutingSessionInfo releasedSession = maybeReleaseMediaStreams(sessionId); + if (releasedSession != null) { + mPendingSystemSessionReleases.put(sessionId, releasedSession); + } else if (!checkSessionIdIsValid(sessionId, "releaseSession")) { + return; + } } addRequestId(requestId); @@ -1054,9 +1074,19 @@ public abstract class MediaRoute2ProviderService extends Service { @Nullable private final AudioPolicy mAudioPolicy; @Nullable private final AudioRecord mAudioRecord; + /** + * Holds the last {@link RoutingSessionInfo} associated with these streams. + * + * @hide + */ + @GuardedBy("MediaRoute2ProviderService.this.mSessionLock") + @NonNull + private RoutingSessionInfo mSessionInfo; + // TODO: b/380431086: Add the video equivalent. private MediaStreams(Builder builder) { + this.mSessionInfo = builder.mSessionInfo; this.mAudioPolicy = builder.mAudioPolicy; this.mAudioRecord = builder.mAudioRecord; } @@ -1077,9 +1107,19 @@ public abstract class MediaRoute2ProviderService extends Service { */ public static final class Builder { + @NonNull private RoutingSessionInfo mSessionInfo; @Nullable private AudioPolicy mAudioPolicy; @Nullable private AudioRecord mAudioRecord; + /** + * Constructor. + * + * @param sessionInfo The {@link RoutingSessionInfo} associated with these streams. + */ + Builder(@NonNull RoutingSessionInfo sessionInfo) { + mSessionInfo = requireNonNull(sessionInfo); + } + /** Populates system media audio-related structures. */ public Builder setAudioStream( @NonNull AudioPolicy audioPolicy, @NonNull AudioRecord audioRecord) { diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java index 31e1eb36ad8d..70f5bb32a5d5 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java @@ -2074,15 +2074,19 @@ public class PackageWatchdog { bootMitigationCounts.put(observer.name, observer.getBootMitigationCount()); } + FileOutputStream fileStream = null; + ObjectOutputStream objectStream = null; try { - FileOutputStream fileStream = new FileOutputStream(new File(filePath)); - ObjectOutputStream objectStream = new ObjectOutputStream(fileStream); + fileStream = new FileOutputStream(new File(filePath)); + objectStream = new ObjectOutputStream(fileStream); objectStream.writeObject(bootMitigationCounts); objectStream.flush(); - objectStream.close(); - fileStream.close(); } catch (Exception e) { Slog.i(TAG, "Could not save observers metadata to file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); } } @@ -2233,23 +2237,32 @@ public class PackageWatchdog { void readAllObserversBootMitigationCountIfNecessary(String filePath) { File metadataFile = new File(filePath); if (metadataFile.exists()) { + FileInputStream fileStream = null; + ObjectInputStream objectStream = null; + HashMap<String, Integer> bootMitigationCounts = null; try { - FileInputStream fileStream = new FileInputStream(metadataFile); - ObjectInputStream objectStream = new ObjectInputStream(fileStream); - HashMap<String, Integer> bootMitigationCounts = + fileStream = new FileInputStream(metadataFile); + objectStream = new ObjectInputStream(fileStream); + bootMitigationCounts = (HashMap<String, Integer>) objectStream.readObject(); - objectStream.close(); - fileStream.close(); - - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - if (bootMitigationCounts.containsKey(observer.name)) { - observer.setBootMitigationCount( - bootMitigationCounts.get(observer.name)); - } - } } catch (Exception e) { Slog.i(TAG, "Could not read observer metadata file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); + } + + if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) { + Slog.i(TAG, "No observer in metadata file"); + return; + } + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + if (bootMitigationCounts.containsKey(observer.name)) { + observer.setBootMitigationCount( + bootMitigationCounts.get(observer.name)); + } } } } diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java index ffae5176cebf..ac815f8aca85 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java @@ -2021,15 +2021,19 @@ public class PackageWatchdog { bootMitigationCounts.put(observer.name, observer.getBootMitigationCount()); } + FileOutputStream fileStream = null; + ObjectOutputStream objectStream = null; try { - FileOutputStream fileStream = new FileOutputStream(new File(filePath)); - ObjectOutputStream objectStream = new ObjectOutputStream(fileStream); + fileStream = new FileOutputStream(new File(filePath)); + objectStream = new ObjectOutputStream(fileStream); objectStream.writeObject(bootMitigationCounts); objectStream.flush(); - objectStream.close(); - fileStream.close(); } catch (Exception e) { Slog.i(TAG, "Could not save observers metadata to file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); } } @@ -2180,23 +2184,32 @@ public class PackageWatchdog { void readAllObserversBootMitigationCountIfNecessary(String filePath) { File metadataFile = new File(filePath); if (metadataFile.exists()) { + FileInputStream fileStream = null; + ObjectInputStream objectStream = null; + HashMap<String, Integer> bootMitigationCounts = null; try { - FileInputStream fileStream = new FileInputStream(metadataFile); - ObjectInputStream objectStream = new ObjectInputStream(fileStream); - HashMap<String, Integer> bootMitigationCounts = + fileStream = new FileInputStream(metadataFile); + objectStream = new ObjectInputStream(fileStream); + bootMitigationCounts = (HashMap<String, Integer>) objectStream.readObject(); - objectStream.close(); - fileStream.close(); - - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - if (bootMitigationCounts.containsKey(observer.name)) { - observer.setBootMitigationCount( - bootMitigationCounts.get(observer.name)); - } - } } catch (Exception e) { Slog.i(TAG, "Could not read observer metadata file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); + } + + if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) { + Slog.i(TAG, "No observer in metadata file"); + return; + } + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + if (bootMitigationCounts.containsKey(observer.name)) { + observer.setBootMitigationCount( + bootMitigationCounts.get(observer.name)); + } } } } diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java index 03a2101544be..218983a55e1b 100644 --- a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java +++ b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java @@ -31,7 +31,6 @@ import androidx.preference.CheckBoxPreference; import androidx.preference.PreferenceViewHolder; import com.android.settingslib.widget.preference.selector.R; -import com.android.settingslib.widget.selectorwithwidgetpreference.flags.Flags; /** * Selector preference (checkbox or radio button) with an optional additional widget. @@ -180,10 +179,8 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference { : getContext().getString(R.string.settings_label)); } - if (Flags.allowSetTitleMaxLines()) { - TextView title = (TextView) holder.findViewById(android.R.id.title); - title.setMaxLines(mTitleMaxLines); - } + TextView title = (TextView) holder.findViewById(android.R.id.title); + title.setMaxLines(mTitleMaxLines); } /** @@ -244,16 +241,12 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference { setLayoutResource(R.layout.preference_selector_with_widget); setIconSpaceReserved(false); - if (Flags.allowSetTitleMaxLines()) { - final TypedArray a = - context.obtainStyledAttributes( - attrs, R.styleable.SelectorWithWidgetPreference, defStyleAttr, - defStyleRes); - mTitleMaxLines = - a.getInt(R.styleable.SelectorWithWidgetPreference_titleMaxLines, - DEFAULT_MAX_LINES); - a.recycle(); - } + final TypedArray a = + context.obtainStyledAttributes( + attrs, R.styleable.SelectorWithWidgetPreference, defStyleAttr, defStyleRes); + mTitleMaxLines = + a.getInt(R.styleable.SelectorWithWidgetPreference_titleMaxLines, DEFAULT_MAX_LINES); + a.recycle(); } @VisibleForTesting(otherwise = VisibleForTesting.NONE) diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 1a043d5015b2..d82b58ec1358 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -97,6 +97,7 @@ flag { name: "settings_catalyst" namespace: "android_settings" description: "Settings catalyst project migration" + is_exported: true bug: "323791114" is_exported: true } @@ -106,6 +107,7 @@ flag { is_fixed_read_only: true namespace: "android_settings" description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop" + is_exported: true bug: "375193223" is_exported: true } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java index 2b8b3b74dab9..c939c770b63d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java @@ -21,9 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import android.app.Application; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.SetFlagsRule; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -33,10 +30,8 @@ import androidx.preference.PreferenceViewHolder; import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.widget.preference.selector.R; -import com.android.settingslib.widget.selectorwithwidgetpreference.flags.Flags; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -45,7 +40,6 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class SelectorWithWidgetPreferenceTest { - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Application mContext; private SelectorWithWidgetPreference mPreference; @@ -128,26 +122,6 @@ public class SelectorWithWidgetPreferenceTest { } @Test - @DisableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES) - public void onBindViewHolder_titleMaxLinesSet_flagOff_titleMaxLinesMatchesDefault() { - final int titleMaxLines = 5; - AttributeSet attributeSet = Robolectric.buildAttributeSet() - .addAttribute(R.attr.titleMaxLines, String.valueOf(titleMaxLines)) - .build(); - mPreference = new SelectorWithWidgetPreference(mContext, attributeSet); - View view = LayoutInflater.from(mContext) - .inflate(mPreference.getLayoutResource(), null /* root */); - PreferenceViewHolder preferenceViewHolder = - PreferenceViewHolder.createInstanceForTests(view); - - mPreference.onBindViewHolder(preferenceViewHolder); - - TextView title = (TextView) preferenceViewHolder.findViewById(android.R.id.title); - assertThat(title.getMaxLines()).isEqualTo(SelectorWithWidgetPreference.DEFAULT_MAX_LINES); - } - - @Test - @EnableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES) public void onBindViewHolder_noTitleMaxLinesSet_titleMaxLinesMatchesDefault() { AttributeSet attributeSet = Robolectric.buildAttributeSet().build(); mPreference = new SelectorWithWidgetPreference(mContext, attributeSet); @@ -163,7 +137,6 @@ public class SelectorWithWidgetPreferenceTest { } @Test - @EnableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES) public void onBindViewHolder_titleMaxLinesSet_titleMaxLinesUpdated() { final int titleMaxLines = 5; AttributeSet attributeSet = Robolectric.buildAttributeSet() diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index 91ac34ac8233..de7c450d8d39 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -148,27 +148,7 @@ public final class DeviceConfigService extends Binder { // TODO(b/364399200): use filter to skip instead? return; } - - ArrayList<String> missingFiles = new ArrayList<String>(); - for (String fileName : sAconfigTextProtoFilesOnDevice) { - File aconfigFile = new File(fileName); - if (!aconfigFile.exists()) { - missingFiles.add(fileName); - } - } - - if (missingFiles.isEmpty()) { - pw.println("\nAconfig flags:"); - for (String name : MyShellCommand.listAllAconfigFlags(iprovider)) { - pw.println(name); - } - } else { - pw.println("\nFailed to dump aconfig flags due to missing files:"); - for (String fileName : missingFiles) { - pw.println(fileName); - } - } - } + } private static HashSet<String> getAconfigFlagNamesInDeviceConfig() { HashSet<String> nameSet = new HashSet<String>(); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 1c4def39eaa0..e01cb84f60ae 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -16,6 +16,19 @@ package com.android.providers.settings; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_DEVICE_SPECIFIC_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_GLOBAL; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCALE; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCK_SETTINGS; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_NETWORK_POLICIES; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SECURE; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS_2; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SYSTEM; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_SETTINGS_BACKUP_DATA; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -99,22 +112,6 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final int NULL_SIZE = -1; private static final float FONT_SCALE_DEF_VALUE = 1.0f; - private static final String KEY_SYSTEM = "system"; - private static final String KEY_SECURE = "secure"; - private static final String KEY_GLOBAL = "global"; - private static final String KEY_LOCALE = "locale"; - private static final String KEY_LOCK_SETTINGS = "lock_settings"; - private static final String KEY_SOFTAP_CONFIG = "softap_config"; - private static final String KEY_NETWORK_POLICIES = "network_policies"; - private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; - private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; - private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; - // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a - // fatal crash. Creating a backup with a different key will prevent Android 12 versions from - // restoring this data. - private static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2"; - private static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data"; - // Versioning of the state file. Increment this version // number any time the set of state items is altered. private static final int STATE_VERSION = 9; @@ -257,6 +254,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) { mBackupRestoreEventLogger = this.getBackupRestoreEventLogger(); + mSettingsHelper.setBackupRestoreEventLogger(mBackupRestoreEventLogger); numberOfSettingsPerKey = new HashMap<>(); areAgentMetricsEnabled = true; } @@ -412,9 +410,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { mSettingsHelper .setLocaleData( localeData, - size, - mBackupRestoreEventLogger, - KEY_LOCALE); + size); break; case KEY_WIFI_CONFIG : @@ -552,8 +548,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (nBytes > buffer.length) buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); mSettingsHelper - .setLocaleData( - buffer, nBytes, mBackupRestoreEventLogger, KEY_LOCALE); + .setLocaleData(buffer, nBytes); // Restore older backups performing the necessary migrations. if (version < FULL_BACKUP_ADDED_WIFI_NEW) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java new file mode 100644 index 000000000000..745c2fb5409d --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 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.providers.settings; + +import android.net.Uri; +import android.provider.Settings; + +/** + * Class to store the keys used for backup and restore. + */ +final class SettingsBackupRestoreKeys { + static final String KEY_UNKNOWN = "unknown"; + static final String KEY_SYSTEM = "system"; + static final String KEY_SECURE = "secure"; + static final String KEY_GLOBAL = "global"; + static final String KEY_LOCALE = "locale"; + static final String KEY_LOCK_SETTINGS = "lock_settings"; + static final String KEY_SOFTAP_CONFIG = "softap_config"; + static final String KEY_NETWORK_POLICIES = "network_policies"; + static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; + static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; + static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; + // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a + // fatal crash. Creating a backup with a different key will prevent Android 12 versions from + // restoring this data. + static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2"; + static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data"; + + /** + * Returns the key corresponding to the given URI. + * + * @param uri The URI of the setting's destination. + * @return The key corresponding to the given URI, or KEY_UNKNOWN if the URI is not recognized. + */ + static String getKeyFromUri(Uri uri) { + if (uri.equals(Settings.Secure.CONTENT_URI)) { + return KEY_SECURE; + } else if (uri.equals(Settings.System.CONTENT_URI)) { + return KEY_SYSTEM; + } else if (uri.equals(Settings.Global.CONTENT_URI)) { + return KEY_GLOBAL; + } else { + return KEY_UNKNOWN; + } + } + +}
\ No newline at end of file diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 924c151a99a0..ab8d739feb43 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -17,6 +17,7 @@ package com.android.providers.settings; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.backup.BackupRestoreEventLogger; @@ -84,6 +85,7 @@ public class SettingsHelper { private Context mContext; private AudioManager mAudioManager; private TelephonyManager mTelephonyManager; + @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger; /** * A few settings elements are special in that a restore of those values needs to @@ -741,11 +743,8 @@ public class SettingsHelper { * * @param data the comma separated BCP-47 language tags in bytes. * @param size the size of the data in bytes. - * @param backupRestoreEventLogger the logger to log the restore event. - * @param dataType the data type of the setting for logging purposes. */ - /* package */ void setLocaleData( - byte[] data, int size, BackupRestoreEventLogger backupRestoreEventLogger, String dataType) { + /* package */ void setLocaleData(byte[] data, int size) { final Configuration conf = mContext.getResources().getConfiguration(); // Replace "_" with "-" to deal with older backups. @@ -772,15 +771,15 @@ public class SettingsHelper { am.updatePersistentConfigurationWithAttribution(config, mContext.getOpPackageName(), mContext.getAttributionTag()); - if (Flags.enableMetricsSettingsBackupAgents()) { - backupRestoreEventLogger - .logItemsRestored(dataType, localeList.size()); + if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) { + mBackupRestoreEventLogger + .logItemsRestored(SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size()); } } catch (RemoteException e) { - if (Flags.enableMetricsSettingsBackupAgents()) { - backupRestoreEventLogger + if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) { + mBackupRestoreEventLogger .logItemsRestoreFailed( - dataType, + SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size(), ERROR_REMOTE_EXCEPTION_SETTING_LOCALE_DATA); } @@ -795,4 +794,13 @@ public class SettingsHelper { AudioManager am = new AudioManager(mContext); am.reloadAudioSettings(); } + + /** + * Sets the backup restore event logger. + * + * @param backupRestoreEventLogger the logger to log B&R metrics. + */ + void setBackupRestoreEventLogger(BackupRestoreEventLogger backupRestoreEventLogger) { + mBackupRestoreEventLogger = backupRestoreEventLogger; + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 55f48e3e367f..f1f03c31f718 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -120,6 +120,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; @@ -2914,6 +2915,14 @@ public class SettingsProvider extends ContentProvider { }; } + @VisibleForTesting + void injectServices(UserManager userManager, IPackageManager packageManager, + SystemConfigManager sysConfigManager) { + mUserManager = userManager; + mPackageManager = packageManager; + mSysConfigManager = sysConfigManager; + } + private static final class Arguments { private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS = Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*"); @@ -3080,6 +3089,7 @@ public class SettingsProvider extends ContentProvider { private static final String SSAID_USER_KEY = "userkey"; + @GuardedBy("mLock") private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>(); private GenerationRegistry mGenerationRegistry; @@ -3992,6 +4002,14 @@ public class SettingsProvider extends ContentProvider { } } + @VisibleForTesting + void injectSettings(SettingsState settings, int type, int userId) { + int key = makeKey(type, userId); + synchronized (mLock) { + mSettingsStates.put(key, settings); + } + } + private final class MyHandler extends Handler { private static final int MSG_NOTIFY_URI_CHANGED = 1; private static final int MSG_NOTIFY_DATA_CHANGED = 2; @@ -4023,12 +4041,21 @@ public class SettingsProvider extends ContentProvider { } } - private final class UpgradeController { + @VisibleForTesting + final class UpgradeController { private static final int SETTINGS_VERSION = 226; private final int mUserId; + private final Injector mInjector; + public UpgradeController(int userId) { + this(/* injector= */ null, userId); + } + + @VisibleForTesting + UpgradeController(Injector injector, int userId) { + mInjector = injector == null ? new Injector() : injector; mUserId = userId; } @@ -6136,8 +6163,8 @@ public class SettingsProvider extends ContentProvider { systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE); final Setting minRefreshRateSetting = systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE); - float highestRefreshRate = RefreshRateSettingsUtils - .findHighestRefreshRateForDefaultDisplay(getContext()); + float highestRefreshRate = + mInjector.findHighestRefreshRateForDefaultDisplay(getContext()); if (!peakRefreshRateSetting.isNull()) { try { @@ -6318,6 +6345,14 @@ public class SettingsProvider extends ContentProvider { private long getBitMask(int capability) { return 1 << (capability - 1); } + + @VisibleForTesting + static class Injector { + float findHighestRefreshRateForDefaultDisplay(Context context) { + return RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay( + context); + } + } } /** diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 5cd534e62ea9..bf3afeda448e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -107,7 +107,7 @@ import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; * the same lock to grab the current state to write to disk. * </p> */ -final class SettingsState { +public class SettingsState { private static final boolean DEBUG = false; private static final boolean DEBUG_PERSISTENCE = false; @@ -1838,7 +1838,7 @@ final class SettingsState { } } - class Setting { + public class Setting { private String name; private String value; private String defaultValue; diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java new file mode 100644 index 000000000000..ef537e8c7fc0 --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 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.providers.settings; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import android.provider.Settings; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link SettingsBackupRestoreKeys}. + */ +@RunWith(AndroidJUnit4.class) +public class SettingsBackupRestoreKeysTest { + + @Test + public void getKeyFromUri_secureUri_returnsSecureKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Secure.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_SECURE); + } + + @Test + public void getKeyFromUri_systemUri_returnsSystemKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.System.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_SYSTEM); + } + + @Test + public void getKeyFromUri_globalUri_returnsGlobalKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Global.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_GLOBAL); + } + + @Test + public void getKeyFromUri_unknownUri_returnsUnknownKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Uri.parse("content://unknown"))) + .isEqualTo(SettingsBackupRestoreKeys.KEY_UNKNOWN); + } +}
\ No newline at end of file diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java new file mode 100644 index 000000000000..26ff376f828e --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java @@ -0,0 +1,175 @@ +/* + * 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.providers.settings; + +import static android.provider.Settings.System.MIN_REFRESH_RATE; +import static android.provider.Settings.System.PEAK_REFRESH_RATE; + +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_GLOBAL; +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SECURE; +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SYSTEM; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.IPackageManager; +import android.os.Looper; +import android.os.SystemConfigManager; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class UpgradeControllerTest { + private static final int USER_ID = UserHandle.USER_SYSTEM; + private static final float HIGHEST_REFRESH_RATE = 130f; + + private final Context mContext = + spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); + private final SettingsProvider.SettingsRegistry.UpgradeController.Injector mInjector = + new SettingsProvider.SettingsRegistry.UpgradeController.Injector() { + @Override + float findHighestRefreshRateForDefaultDisplay(Context context) { + return HIGHEST_REFRESH_RATE; + } + }; + private final SettingsProvider mSettingsProvider = new SettingsProvider() { + @Override + public boolean onCreate() { + return true; + } + }; + private final SettingsProvider.SettingsRegistry mSettingsRegistry = + mSettingsProvider.new SettingsRegistry(Looper.getMainLooper()); + private final SettingsProvider.SettingsRegistry.UpgradeController mUpgradeController = + mSettingsRegistry.new UpgradeController(mInjector, USER_ID); + + @Mock + private UserManager mUserManager; + + @Mock + private IPackageManager mPackageManager; + + @Mock + private SystemConfigManager mSysConfigManager; + + @Mock + private SettingsState mSystemSettings; + + @Mock + private SettingsState mSecureSettings; + + @Mock + private SettingsState mGlobalSettings; + + @Mock + private SettingsState.Setting mMockSetting; + + @Mock + private SettingsState.Setting mPeakRefreshRateSetting; + + @Mock + private SettingsState.Setting mMinRefreshRateSetting; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mSettingsProvider.attachInfoForTesting(mContext, /* info= */ null); + mSettingsProvider.injectServices(mUserManager, mPackageManager, mSysConfigManager); + when(mSystemSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mSecureSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mGlobalSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mMockSetting.isNull()).thenReturn(true); + when(mMockSetting.getValue()).thenReturn("0"); + + when(mSystemSettings.getSettingLocked(PEAK_REFRESH_RATE)) + .thenReturn(mPeakRefreshRateSetting); + when(mSystemSettings.getSettingLocked(MIN_REFRESH_RATE)) + .thenReturn(mMinRefreshRateSetting); + + mSettingsRegistry.injectSettings(mSystemSettings, SETTINGS_TYPE_SYSTEM, USER_ID); + mSettingsRegistry.injectSettings(mSecureSettings, SETTINGS_TYPE_SECURE, USER_ID); + mSettingsRegistry.injectSettings(mGlobalSettings, SETTINGS_TYPE_GLOBAL, USER_ID); + + // Lowest version so that all upgrades are run + when(mSecureSettings.getVersionLocked()).thenReturn(118); + } + + @Test + public void testUpgrade_refreshRateSettings_defaultValues() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(true); + when(mMinRefreshRateSetting.isNull()).thenReturn(true); + + mUpgradeController.upgradeIfNeededLocked(); + + // Should remain unchanged + verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + } + + @Test + public void testUpgrade_refreshRateSettings_enabled() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(false); + when(mMinRefreshRateSetting.isNull()).thenReturn(false); + when(mPeakRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE)); + when(mMinRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE)); + + mUpgradeController.upgradeIfNeededLocked(); + + // Highest refresh rate gets converted to infinity + verify(mSystemSettings).insertSettingLocked(eq(PEAK_REFRESH_RATE), + eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(), + /* makeDefault= */ anyBoolean(), /* packageName= */ any()); + verify(mSystemSettings).insertSettingLocked(eq(MIN_REFRESH_RATE), + eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(), + /* makeDefault= */ anyBoolean(), /* packageName= */ any()); + } + + @Test + public void testUpgrade_refreshRateSettings_disabled() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(false); + when(mMinRefreshRateSetting.isNull()).thenReturn(false); + when(mPeakRefreshRateSetting.getValue()).thenReturn("70f"); + when(mMinRefreshRateSetting.getValue()).thenReturn("70f"); + + mUpgradeController.upgradeIfNeededLocked(); + + // Should remain unchanged + verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + } +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index c4d13ba6effa..5ff2d1b07347 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -496,6 +496,13 @@ flag { } flag { + name: "status_bar_popup_chips" + namespace: "systemui" + description: "Show rich ongoing processes as chips in the status bar" + bug: "372964148" +} + +flag { name: "promote_notifications_automatically" namespace: "systemui" description: "Flag to automatically turn certain notifications into promoted notifications so " @@ -1237,6 +1244,13 @@ flag { } flag { + name: "glanceable_hub_back_action" + namespace: "systemui" + description: "Support back action from glanceable hub" + bug: "382771533" +} + +flag { name: "dream_overlay_updated_font" namespace: "systemui" description: "Flag to enable updated font settings for dream overlay" diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java index ca2b9578f2be..7d27a562f536 100644 --- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java @@ -195,7 +195,10 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { // Create the origin leash and add to the transition root leash. mOriginLeash = new SurfaceControl.Builder().setName("OriginTransition-origin-leash").build(); - mStartTransaction + + // Create temporary transaction to build + final SurfaceControl.Transaction tmpTransaction = new SurfaceControl.Transaction(); + tmpTransaction .reparent(mOriginLeash, rootLeash) .show(mOriginLeash) .setCornerRadius(mOriginLeash, windowRadius) @@ -208,14 +211,14 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { int mode = change.getMode(); SurfaceControl leash = change.getLeash(); // Reparent leash to the transition root. - mStartTransaction.reparent(leash, rootLeash); + tmpTransaction.reparent(leash, rootLeash); if (TransitionUtil.isOpeningMode(mode)) { openingSurfaces.add(change.getLeash()); // For opening surfaces, ending bounds are base bound. Apply corner radius if // it's full screen. Rect bounds = change.getEndAbsBounds(); if (displayBounds.equals(bounds)) { - mStartTransaction + tmpTransaction .setCornerRadius(leash, windowRadius) .setWindowCrop(leash, bounds.width(), bounds.height()); } @@ -226,28 +229,53 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { // it's full screen. Rect bounds = change.getStartAbsBounds(); if (displayBounds.equals(bounds)) { - mStartTransaction + tmpTransaction .setCornerRadius(leash, windowRadius) .setWindowCrop(leash, bounds.width(), bounds.height()); } } } + if (openingSurfaces.isEmpty() && closingSurfaces.isEmpty()) { + logD("prepareUIs: no opening/closing surfaces available, nothing to prepare."); + return false; + } + // Set relative order: // ---- App1 ---- // ---- origin ---- // ---- App2 ---- + if (mIsEntry) { - mStartTransaction - .setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1) - .setRelativeLayer( - openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1); + if (!closingSurfaces.isEmpty()) { + tmpTransaction + .setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1); + } else { + logW("Missing closing surface is entry transition"); + } + if (!openingSurfaces.isEmpty()) { + tmpTransaction + .setRelativeLayer( + openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1); + } else { + logW("Missing opening surface is entry transition"); + } + } else { - mStartTransaction - .setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1) - .setRelativeLayer( - closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1); + if (!openingSurfaces.isEmpty()) { + tmpTransaction + .setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1); + } else { + logW("Missing opening surface is exit transition"); + } + if (!closingSurfaces.isEmpty()) { + tmpTransaction.setRelativeLayer( + closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1); + } else { + logW("Missing closing surface is exit transition"); + } } + mStartTransaction.merge(tmpTransaction); // Attach origin UIComponent to origin leash. mOriginTransaction = mOrigin.newTransaction(); @@ -300,6 +328,7 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { } private void cancel() { + logD("cancel()"); if (mAnimator != null) { mAnimator.cancel(); } @@ -311,6 +340,10 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { } } + private static void logW(String msg) { + Log.w(TAG, msg); + } + private static void logE(String msg) { Log.e(TAG, msg); } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 5dbedc7045e4..bf3360f0ea14 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -931,7 +931,9 @@ private fun BoxScope.CommunalHubLazyGrid( Modifier.requiredSize(dpSize) .thenIf(!isItemDragging) { Modifier.animateItem( - placementSpec = spring(stiffness = Spring.StiffnessMediumLow) + placementSpec = spring(stiffness = Spring.StiffnessMediumLow), + // See b/376495198 - not supported with AndroidView + fadeOutSpec = null, ) } .thenIf(isItemDragging) { Modifier.zIndex(1f) }, @@ -980,11 +982,14 @@ private fun BoxScope.CommunalHubLazyGrid( size = size, selected = false, modifier = - Modifier.requiredSize(dpSize).animateItem().thenIf( - communalResponsiveGrid() - ) { - Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } - }, + Modifier.requiredSize(dpSize) + .animateItem( + // See b/376495198 - not supported with AndroidView + fadeOutSpec = null + ) + .thenIf(communalResponsiveGrid()) { + Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } + }, index = index, contentListState = contentListState, interactionHandler = interactionHandler, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index db33e7c628d7..79cf24b9c547 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -35,9 +35,8 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.MutableSceneTransitionLayoutState -import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.modifiers.thenIf import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene @@ -61,7 +60,7 @@ constructor( private val clockInteractor: KeyguardClockInteractor, ) { @Composable - fun SceneScope.DefaultClockLayout( + fun ContentScope.DefaultClockLayout( smartSpacePaddingTop: (Resources) -> Int, isShadeLayoutWide: Boolean, modifier: Modifier = Modifier, @@ -95,7 +94,7 @@ constructor( } Column(modifier) { - SceneTransitionLayout(state) { + NestedSceneTransitionLayout(state, Modifier) { scene(splitShadeLargeClockScene) { LargeClockWithSmartSpace( smartSpacePaddingTop = smartSpacePaddingTop, @@ -134,7 +133,7 @@ constructor( } @Composable - private fun SceneScope.SmallClockWithSmartSpace( + private fun ContentScope.SmallClockWithSmartSpace( smartSpacePaddingTop: (Resources) -> Int, modifier: Modifier = Modifier, ) { @@ -159,7 +158,7 @@ constructor( } @Composable - private fun SceneScope.LargeClockWithSmartSpace( + private fun ContentScope.LargeClockWithSmartSpace( smartSpacePaddingTop: (Resources) -> Int, shouldOffSetClockToOneHalf: Boolean = false, ) { @@ -200,7 +199,7 @@ constructor( } @Composable - private fun SceneScope.WeatherLargeClockWithSmartSpace( + private fun ContentScope.WeatherLargeClockWithSmartSpace( smartSpacePaddingTop: (Resources) -> Int, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt index 2af5ffaee7ed..5790c4af0d77 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt @@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.layoutId import com.android.compose.animation.scene.ContentScope @@ -84,7 +85,11 @@ constructor( viewModel.notificationsPlaceholderViewModelFactory.create() } - OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) { + OverlayShade( + panelAlignment = Alignment.TopStart, + modifier = modifier, + onScrimClicked = viewModel::onScrimClicked, + ) { Column { if (viewModel.showHeader) { val burnIn = rememberBurnIn(clockInteractor) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index b1a19456ab7d..f6c5f588aa95 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -99,7 +99,11 @@ constructor( val viewModel = rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() } - OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) { + OverlayShade( + panelAlignment = Alignment.TopEnd, + modifier = modifier, + onScrimClicked = viewModel::onScrimClicked, + ) { Column { ExpandedShadeHeader( viewModelFactory = viewModel.shadeHeaderViewModelFactory, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 55fafd5cfeca..8907aec7fd48 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.gestures.Orientation import com.android.compose.animation.scene.ProgressConverter import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.transitions -import com.android.systemui.bouncer.ui.composable.Bouncer import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes @@ -110,17 +109,13 @@ val SceneContainerTransitions = transitions { // Overlay transitions - // TODO(b/376659778): Remove this transition once nested STLs are supported. - from(Scenes.Gone, to = Overlays.NotificationsShade) { - toNotificationsShadeTransition(translateClock = true) - } to(Overlays.NotificationsShade) { toNotificationsShadeTransition() } to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() } from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) { notificationsShadeToQuickSettingsShadeTransition() } from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) { - toNotificationsShadeTransition(translateClock = true, durationScale = 0.9) + toNotificationsShadeTransition(durationScale = 0.9) } from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) { toQuickSettingsShadeTransition(durationScale = 0.9) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt index 6bdb36331709..3d62151baf2f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt @@ -29,10 +29,7 @@ import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.Shade import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.toNotificationsShadeTransition( - translateClock: Boolean = false, - durationScale: Double = 1.0, -) { +fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) swipeSpec = spring( @@ -45,11 +42,6 @@ fun TransitionBuilder.toNotificationsShadeTransition( elevateInContent = Overlays.NotificationsShade, ) scaleSize(OverlayShade.Elements.Panel, height = 0f) - // TODO(b/376659778): This is a temporary hack to have a shared element transition with the - // lockscreen clock. Remove once nested STLs are supported. - if (!translateClock) { - translate(ClockElementKeys.smallClockElementKey) - } // Avoid translating the status bar with the shade panel. translate(NotificationsShade.Elements.StatusBar) // Slide in the shade panel from the top edge. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index 8a5c96da5ac6..cfbe6671db02 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -20,6 +20,9 @@ package com.android.systemui.shade.ui.composable import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.PaddingValues @@ -41,30 +44,51 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.effect.rememberOffsetOverscrollEffect import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.res.R /** Renders a lightweight shade UI container, as an overlay. */ @Composable -fun SceneScope.OverlayShade( +fun ContentScope.OverlayShade( + panelAlignment: Alignment, onScrimClicked: () -> Unit, modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - Box(modifier) { + // TODO(b/384653288) This should be removed when b/378470603 is done. + val idleEffect = rememberOffsetOverscrollEffect(Orientation.Vertical) + Box( + modifier + .overscroll(idleEffect) + .nestedScroll( + remember { + object : NestedScrollConnection { + override suspend fun onPreFling(available: Velocity): Velocity { + return available + } + } + } + ) + .scrollable(rememberScrollableState { 0f }, Orientation.Vertical, idleEffect) + ) { Scrim(onClicked = onScrimClicked) - Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = Alignment.TopEnd) { + Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = panelAlignment) { Panel( modifier = Modifier.element(OverlayShade.Elements.Panel) @@ -77,7 +101,7 @@ fun SceneScope.OverlayShade( } @Composable -private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) { +private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) { Spacer( modifier = modifier @@ -89,7 +113,7 @@ private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifie } @Composable -private fun SceneScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) { +private fun ContentScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) { Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) { Spacer( modifier = @@ -180,7 +204,6 @@ object OverlayShade { object Dimensions { val PanelCornerRadius = 46.dp - val OverscrollLimit = 32.dp } object Shapes { diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt index 85bdf9264467..cea1e9600741 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt @@ -163,6 +163,16 @@ class KeyguardDisplayManagerTest : SysuiTestCase() { } @Test + fun testShow_rearDisplayOuterDefaultActive_occluded() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + + whenever(deviceStateHelper.isRearDisplayOuterDefaultActive(secondaryDisplay)) + .thenReturn(true) + whenever(keyguardStateController.isOccluded).thenReturn(true) + verify(presentationFactory, never()).create(eq(secondaryDisplay)) + } + + @Test fun testShow_presentationCreated() { displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 41cc6ee182cf..271cd3a4f202 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.back.domain.interactor +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider @@ -31,6 +32,7 @@ import androidx.test.filters.SmallTest import com.android.internal.statusbar.IStatusBarService import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope @@ -93,6 +95,7 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher @Mock private lateinit var iStatusBarService: IStatusBarService @Mock private lateinit var headsUpManager: HeadsUpManager + @Mock private lateinit var communalBackActionInteractor: CommunalBackActionInteractor private val keyguardRepository = FakeKeyguardRepository() private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { @@ -117,6 +120,7 @@ class BackActionInteractorTest : SysuiTestCase() { windowRootViewVisibilityInteractor, shadeBackActionInteractor, qsController, + communalBackActionInteractor, ) } @@ -306,6 +310,19 @@ class BackActionInteractorTest : SysuiTestCase() { verify(shadeBackActionInteractor).onBackProgressed(0.4f) } + @Test + @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_BACK_ACTION) + fun onBackAction_communalCanBeDismissed_communalBackActionInteractorCalled() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + powerInteractor.setAwakeForTest() + val callback = getBackInvokedCallback() + whenever(communalBackActionInteractor.canBeDismissed()).thenReturn(true) + callback.onBackInvoked() + + verify(communalBackActionInteractor).onBackPressed() + } + private fun getBackInvokedCallback(): OnBackInvokedCallback { testScope.runCurrent() val captor = argumentCaptor<OnBackInvokedCallback>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt new file mode 100644 index 000000000000..c365f1cb3872 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class CommunalBackActionInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private var Kosmos.underTest by Fixture { communalBackActionInteractor } + + @Test + @EnableFlags(FLAG_COMMUNAL_HUB) + fun communalShowing_canBeDismissed() = + kosmos.runTest { + setCommunalAvailable(true) + assertThat(underTest.canBeDismissed()).isEqualTo(false) + communalInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + assertThat(underTest.canBeDismissed()).isEqualTo(true) + } + + @Test + @EnableFlags(FLAG_COMMUNAL_HUB) + fun onBackPressed_invokesSceneChange() = + kosmos.runTest { + underTest.onBackPressed() + runCurrent() + assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 0bfcd242828d..8a9c42d9b64e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -130,19 +130,6 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { } @Test - fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() = - testScope.runTest { - val tutorialSettingState by - collectLastValue(communalTutorialRepository.tutorialSettingState) - - communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) - - goToCommunal() - - assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED) - } - - @Test fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() = testScope.runTest { val tutorialSettingState by diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt index ee3e241b5754..56e8185ab580 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt @@ -81,7 +81,7 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { // Then verify(cameraGestureHelper) .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true), result) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index 50ac26196978..fde9b8ce6a50 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -197,7 +197,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { val dndMode = currentModes!!.single() assertThat(dndMode.isActive).isFalse() - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) } @Test @@ -222,7 +222,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { ) // then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) assertEquals(ZEN_MODE_OFF, spyZenMode.value) assertNull(spyConditionId.value) } @@ -244,7 +244,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { val dndMode = currentModes!!.single() assertThat(dndMode.isActive).isTrue() assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull() - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) } @Test @@ -268,7 +268,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { ) // then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) assertNull(spyConditionId.value) } @@ -285,7 +285,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { val result = underTest.onTriggered(null) runCurrent() - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) val dndMode = currentModes!!.single() assertThat(dndMode.isActive).isTrue() assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)) @@ -313,7 +313,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { ) // then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) assertEquals(conditionUri, spyConditionId.value) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt new file mode 100644 index 000000000000..18946f9d7e07 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceHapticViewModelFactory +import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel +import com.android.systemui.kosmos.testScope +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class KeyguardQuickAffordanceHapticViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + private val configKey = "$slotId::home" + private val keyguardQuickAffordanceInteractor = kosmos.keyguardQuickAffordanceInteractor + private val viewModelFlow = + MutableStateFlow(KeyguardQuickAffordanceViewModel(configKey = configKey, slotId = slotId)) + + private val underTest = + kosmos.keyguardQuickAffordanceHapticViewModelFactory.create(viewModelFlow) + + @Test + fun whenLaunchingFromTriggeredResult_hapticStateIsLaunch() = + testScope.runTest { + // GIVEN that the result from triggering the affordance launched an activity or dialog + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(true, configKey) + ) + runCurrent() + + // THEN the haptic state indicates that a launch haptics must play + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH) + } + + @Test + fun whenNotLaunchFromTriggeredResult_hapticStateDoesNotEmit() = + testScope.runTest { + // GIVEN that the result from triggering the affordance did not launch an activity or + // dialog + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(false, configKey) + ) + runCurrent() + + // THEN there is no haptic state to play any feedback + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.NO_HAPTICS) + } + + @Test + fun onQuickAffordanceTogglesToActivated_hapticStateIsToggleOn() = + testScope.runTest { + // GIVEN that an affordance toggles from deactivated to activated + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + toggleQuickAffordance(on = true) + + // THEN the haptic state reflects that a toggle on haptics should play + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_ON) + } + + @Test + fun onQuickAffordanceTogglesToDeactivated_hapticStateIsToggleOff() = + testScope.runTest { + // GIVEN that an affordance toggles from activated to deactivated + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + toggleQuickAffordance(on = false) + + // THEN the haptic state reflects that a toggle off haptics should play + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_OFF) + } + + private fun TestScope.toggleQuickAffordance(on: Boolean) { + underTest.updateActivatedHistory(!on) + runCurrent() + underTest.updateActivatedHistory(on) + runCurrent() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt index b15352bfe6ab..173b4e56075c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt @@ -49,14 +49,10 @@ import org.mockito.MockitoAnnotations class MuteQuickAffordanceConfigTest : SysuiTestCase() { private lateinit var underTest: MuteQuickAffordanceConfig - @Mock - private lateinit var ringerModeTracker: RingerModeTracker - @Mock - private lateinit var audioManager: AudioManager - @Mock - private lateinit var userTracker: UserTracker - @Mock - private lateinit var userFileManager: UserFileManager + @Mock private lateinit var ringerModeTracker: RingerModeTracker + @Mock private lateinit var audioManager: AudioManager + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var userFileManager: UserFileManager private lateinit var testDispatcher: TestDispatcher private lateinit var testScope: TestScope @@ -70,9 +66,12 @@ class MuteQuickAffordanceConfigTest : SysuiTestCase() { whenever(userTracker.userContext).thenReturn(context) whenever(userFileManager.getSharedPreferences(any(), any(), any())) - .thenReturn(context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE)) + .thenReturn( + context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE) + ) - underTest = MuteQuickAffordanceConfig( + underTest = + MuteQuickAffordanceConfig( context, userTracker, userFileManager, @@ -81,64 +80,71 @@ class MuteQuickAffordanceConfigTest : SysuiTestCase() { testScope.backgroundScope, testDispatcher, testDispatcher, - ) + ) } @Test - fun pickerState_volumeFixed_notAvailable() = testScope.runTest { - //given - whenever(audioManager.isVolumeFixed).thenReturn(true) + fun pickerState_volumeFixed_notAvailable() = + testScope.runTest { + // given + whenever(audioManager.isVolumeFixed).thenReturn(true) - //when - val result = underTest.getPickerScreenState() + // when + val result = underTest.getPickerScreenState() - //then - assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result) - } + // then + assertEquals( + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, + result, + ) + } @Test - fun pickerState_volumeNotFixed_available() = testScope.runTest { - //given - whenever(audioManager.isVolumeFixed).thenReturn(false) + fun pickerState_volumeNotFixed_available() = + testScope.runTest { + // given + whenever(audioManager.isVolumeFixed).thenReturn(false) - //when - val result = underTest.getPickerScreenState() + // when + val result = underTest.getPickerScreenState() - //then - assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result) - } + // then + assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result) + } @Test - fun triggered_stateWasPreviouslyNORMAL_currentlySILENT_moveToPreviousState() = testScope.runTest { - //given - val ringerModeCapture = argumentCaptor<Int>() - whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) - underTest.onTriggered(null) - whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT) - - //when - val result = underTest.onTriggered(null) - runCurrent() - verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture() - - //then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) - assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value) - } + fun triggered_stateWasPreviouslyNORMAL_currentlySILENT_moveToPreviousState() = + testScope.runTest { + // given + val ringerModeCapture = argumentCaptor<Int>() + whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) + underTest.onTriggered(null) + whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT) + + // when + val result = underTest.onTriggered(null) + runCurrent() + verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture() + + // then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) + assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value) + } @Test - fun triggered_stateIsNotSILENT_moveToSILENTringer() = testScope.runTest { - //given - val ringerModeCapture = argumentCaptor<Int>() - whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) - - //when - val result = underTest.onTriggered(null) - runCurrent() - verify(audioManager).ringerModeInternal = ringerModeCapture.capture() - - //then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) - assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value) - } -}
\ No newline at end of file + fun triggered_stateIsNotSILENT_moveToSILENTringer() = + testScope.runTest { + // given + val ringerModeCapture = argumentCaptor<Int>() + whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) + + // when + val result = underTest.onTriggered(null) + runCurrent() + verify(audioManager).ringerModeInternal = ringerModeCapture.capture() + + // then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) + assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index e9b36b8b3b57..9bdc363b3a38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -88,9 +88,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { Icon.Loaded( drawable = ICON, contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), + ContentDescription.Resource(res = R.string.accessibility_wallet_button), ) ) } @@ -118,9 +116,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { Icon.Loaded( drawable = ICON, contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), + ContentDescription.Resource(res = R.string.accessibility_wallet_button), ) ) } @@ -163,13 +159,9 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } assertThat(underTest.onTriggered(expandable)) - .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled) + .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true)) verify(walletController) - .startQuickAccessUiIntent( - activityStarter, - animationController, - /* hasCard= */ true, - ) + .startQuickAccessUiIntent(activityStarter, animationController, /* hasCard= */ true) } @Test @@ -184,9 +176,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun getPickerScreenState_unavailable() = testScope.runTest { - setUpState( - isWalletServiceAvailable = false, - ) + setUpState(isWalletServiceAvailable = false) assertThat(underTest.getPickerScreenState()) .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) @@ -195,9 +185,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = testScope.runTest { - setUpState( - isWalletFeatureAvailable = false, - ) + setUpState(isWalletFeatureAvailable = false) assertThat(underTest.getPickerScreenState()) .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) @@ -206,9 +194,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun getPickerScreenState_disabledWhenThereIsNoCard() = testScope.runTest { - setUpState( - hasSelectedCard = false, - ) + setUpState(hasSelectedCard = false) assertThat(underTest.getPickerScreenState()) .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) @@ -219,7 +205,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { isWalletServiceAvailable: Boolean = true, isWalletQuerySuccessful: Boolean = true, hasSelectedCard: Boolean = true, - cardType: Int = WalletCard.CARD_TYPE_UNKNOWN + cardType: Int = WalletCard.CARD_TYPE_UNKNOWN, ) { val walletClient: QuickAccessWalletClient = mock() whenever(walletClient.tileIcon).thenReturn(ICON) @@ -242,11 +228,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { /*cardType= */ cardType, /*cardImage= */ mock(), /*contentDescription= */ CARD_DESCRIPTION, - /*pendingIntent= */ mock() + /*pendingIntent= */ mock(), ) .build() ), - 0 + 0, ) } else { GetWalletCardsResponse(emptyList(), 0) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 46d1ebe75899..9de0215022e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -764,6 +764,28 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(launchingAffordance).isFalse() } + @Test + fun onQuickAffordanceTriggered_updatesLaunchingFromTriggeredResult() = + testScope.runTest { + // WHEN selecting and triggering a quick affordance at a slot + val key = homeControls.key + val slot = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + val encodedKey = "$slot::$key" + val actionLaunched = true + homeControls.onTriggeredResult = + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(actionLaunched) + underTest.select(slot, key) + runCurrent() + underTest.onQuickAffordanceTriggered(encodedKey, expandable = null, slot) + + // THEN the latest triggered result shows that an action launched for the same key and + // slot + val launchingFromTriggeredResult by + collectLastValue(underTest.launchingFromTriggeredResult) + assertThat(launchingFromTriggeredResult?.launched).isEqualTo(actionLaunched) + assertThat(launchingFromTriggeredResult?.configKey).isEqualTo(encodedKey) + } + companion object { private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 private val ICON: Icon = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt index ad5eeabf83d2..26fe379f00bf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -118,6 +119,50 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() { ) } + /** STL: Ls -> overlay, then settle with Idle(overlay). */ + @Test + fun transition_overlay_from_ls_scene_end_in_gone() = + testScope.runTest { + sceneTransitions.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Scenes.Lockscreen, + toContent = Overlays.NotificationsShade, + currentScene = Scenes.Lockscreen, + currentOverlays = flowOf(emptySet()), + progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f) + + sceneTransitions.value = + ObservableTransitionState.Idle( + Scenes.Lockscreen, + setOf(Overlays.NotificationsShade), + ) + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + /** * STL: Ls -> Gone, then settle with Idle(Ls). KTF in this scenario needs to invert the * transition LS -> UNDEFINED to UNDEFINED -> LS as there is no mechanism in KTF to @@ -259,6 +304,47 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() { ) } + /** STL: Ls with overlay, then settle with Idle(Ls). */ + @Test + fun transition_overlay_to_ls_scene_end_in_ls() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Overlays.NotificationsShade, + toContent = Scenes.Lockscreen, + currentScene = Scenes.Lockscreen, + currentOverlays = flowOf(setOf(Overlays.NotificationsShade)), + progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + /** STL: Gone -> Ls (AOD), will transition to AOD once */ @Test fun transition_to_ls_scene_with_changed_next_scene_is_respected_just_once() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 0e3b03f74c02..be504cc0f704 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -18,11 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.DisableSceneContainer @@ -75,7 +72,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { private val burnInFlow = MutableStateFlow(BurnInModel()) @Before - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @DisableSceneContainer fun setUp() { MockitoAnnotations.initMocks(this) @@ -219,50 +215,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() = - testScope.runTest { - underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) - val movement by collectLastValue(underTest.movement) - assertThat(movement?.translationX).isEqualTo(0) - - // Set to dozing (on AOD) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED, - ), - validateStep = false, - ) - - // Trigger a change to the burn-in model - burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f) - assertThat(movement?.translationX).isEqualTo(20) - // -20 instead of -30, due to inset of 80 - assertThat(movement?.translationY).isEqualTo(-20) - assertThat(movement?.scale).isEqualTo(0.5f) - assertThat(movement?.scaleClockOnly).isEqualTo(true) - - // Set to the beginning of GONE->AOD transition - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED, - ), - validateStep = false, - ) - assertThat(movement?.translationX).isEqualTo(0) - assertThat(movement?.translationY).isEqualTo(0) - assertThat(movement?.scale).isEqualTo(1f) - assertThat(movement?.scaleClockOnly).isEqualTo(true) - } - - @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() = testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) @@ -334,7 +286,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, @@ -344,7 +295,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, @@ -354,7 +304,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, @@ -364,7 +313,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, @@ -373,7 +321,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer fun translationAndScale_sceneContainerOn_weatherLargeClock() = testBurnInViewModelForClocks( @@ -383,7 +330,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer fun translationAndScale_sceneContainerOn_weatherSmallClock() = testBurnInViewModelForClocks( @@ -393,7 +339,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() = testBurnInViewModelForClocks( @@ -403,7 +348,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer @Ignore("b/367659687") fun translationAndScale_sceneContainerOn_nonWeatherSmallClock() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 95ffc962797d..789477e38b55 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -19,12 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.view.View import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState -import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.shared.model.CommunalScenes @@ -73,7 +71,6 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt index 056efb34a0b1..c47a412e226a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt @@ -117,7 +117,6 @@ class QSTileViewModelImplTest : SysuiTestCase() { "test_spec:\n" + " QSTileState(" + "icon=Resource(res=0, contentDescription=Resource(res=0)), " + - "iconRes=null, " + "label=test_data, " + "activationState=INACTIVE, " + "secondaryLabel=null, " + diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java index b88861756889..5c6657b98801 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java @@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -231,7 +232,8 @@ public class InternetAdapterTest extends SysuiTestCase { mViewHolder.onWifiClick(mWifiEntry, mock(View.class)); - verify(mSpyContext).startActivity(any()); + verify(mInternetDialogController).startActivityForDialog(any()); + verify(mSpyContext, never()).startActivity(any()); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt index 00460bfe83b2..557f4ea262a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt @@ -93,8 +93,7 @@ class AirplaneModeMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.airplane_mode) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt index 632aae035ede..24e46686e23d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt @@ -178,8 +178,7 @@ class AlarmTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.status_bar_alarm) return QSTileState( - Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null), - R.drawable.ic_alarm, + Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null, R.drawable.ic_alarm), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt index 5385f945946c..2ddaddd5ad35 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt @@ -253,8 +253,7 @@ class BatterySaverTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.battery_detail_switch_title) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt index 356b98eb192e..a3c159820a94 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt @@ -77,8 +77,11 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.quick_settings_color_correction_label) return QSTileState( - Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null), - R.drawable.ic_qs_color_correction, + Icon.Loaded( + context.getDrawable(R.drawable.ic_qs_color_correction)!!, + null, + R.drawable.ic_qs_color_correction, + ), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt index 8236c4c1e638..608adf183163 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt @@ -253,7 +253,6 @@ class CustomTileMapperTest : SysuiTestCase() { ): QSTileState { return QSTileState( icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) }, - null, "test label", activationState, "test subtitle", diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt index 587585ccee2e..a115c1235210 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt @@ -73,7 +73,11 @@ class FlashlightMapperTest : SysuiTestCase() { mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true)) val expectedIcon = - Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null) + Icon.Loaded( + context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, + null, + R.drawable.qs_flashlight_icon_on, + ) val actualIcon = tileState.icon assertThat(actualIcon).isEqualTo(expectedIcon) } @@ -84,7 +88,11 @@ class FlashlightMapperTest : SysuiTestCase() { mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false)) val expectedIcon = - Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null) + Icon.Loaded( + context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, + null, + R.drawable.qs_flashlight_icon_off, + ) val actualIcon = tileState.icon assertThat(actualIcon).isEqualTo(expectedIcon) } @@ -95,7 +103,11 @@ class FlashlightMapperTest : SysuiTestCase() { mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable) val expectedIcon = - Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null) + Icon.Loaded( + context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, + null, + R.drawable.qs_flashlight_icon_off, + ) val actualIcon = tileState.icon assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt index e81771ec38d5..8f8f7105415f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt @@ -58,8 +58,11 @@ class FontScalingTileMapperTest : SysuiTestCase() { private fun createFontScalingTileState(): QSTileState = QSTileState( - Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null), - R.drawable.ic_qs_font_scaling, + Icon.Loaded( + context.getDrawable(R.drawable.ic_qs_font_scaling)!!, + null, + R.drawable.ic_qs_font_scaling, + ), context.getString(R.string.quick_settings_font_scaling_label), QSTileState.ActivationState.ACTIVE, null, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt index 12d604ff6a7c..3d3447da15a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt @@ -102,8 +102,7 @@ class HearingDevicesTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_hearing_devices_label) val iconRes = R.drawable.qs_hearing_devices_icon return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt index 9dcf49e02697..b087bbc29bf7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt @@ -82,7 +82,6 @@ class InternetTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.ACTIVE, context.getString(R.string.quick_settings_networks_available), Icon.Loaded(signalDrawable, null), - null, context.getString(R.string.quick_settings_internet_label), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) @@ -120,8 +119,11 @@ class InternetTileMapperTest : SysuiTestCase() { createInternetTileState( QSTileState.ActivationState.ACTIVE, inputModel.secondaryLabel.loadText(context).toString(), - Icon.Loaded(context.getDrawable(expectedSatIcon!!.res)!!, null), - expectedSatIcon.res, + Icon.Loaded( + context.getDrawable(expectedSatIcon!!.res)!!, + null, + expectedSatIcon.res, + ), expectedSatIcon.contentDescription.loadContentDescription(context).toString(), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) @@ -144,8 +146,7 @@ class InternetTileMapperTest : SysuiTestCase() { createInternetTileState( QSTileState.ActivationState.ACTIVE, context.getString(R.string.quick_settings_networks_available), - Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null), - wifiRes, + Icon.Loaded(context.getDrawable(wifiRes)!!, null, wifiRes), context.getString(R.string.quick_settings_internet_label), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) @@ -171,8 +172,8 @@ class InternetTileMapperTest : SysuiTestCase() { Icon.Loaded( context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!, contentDescription = null, + R.drawable.ic_qs_no_internet_unavailable, ), - R.drawable.ic_qs_no_internet_unavailable, context.getString(R.string.quick_settings_networks_unavailable), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) @@ -182,13 +183,11 @@ class InternetTileMapperTest : SysuiTestCase() { activationState: QSTileState.ActivationState, secondaryLabel: String, icon: Icon, - iconRes: Int? = null, contentDescription: String, ): QSTileState { val label = context.getString(R.string.quick_settings_internet_label) return QSTileState( icon, - iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt index 30fce73e04da..780d58c83e5b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt @@ -90,8 +90,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.quick_settings_inversion_label) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt index 37e8a6053682..4ebe23dcdef1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -69,7 +69,12 @@ class LocationTileMapperTest : SysuiTestCase() { fun mapsEnabledDataToOnIconState() { val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) - val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null) + val expectedIcon = + Icon.Loaded( + context.getDrawable(R.drawable.qs_location_icon_on)!!, + null, + R.drawable.qs_location_icon_on, + ) val actualIcon = tileState.icon Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } @@ -78,7 +83,12 @@ class LocationTileMapperTest : SysuiTestCase() { fun mapsDisabledDataToOffIconState() { val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) - val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null) + val expectedIcon = + Icon.Loaded( + context.getDrawable(R.drawable.qs_location_icon_off)!!, + null, + R.drawable.qs_location_icon_off, + ) val actualIcon = tileState.icon Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt index de3dc5730421..44e6b4d2d0f6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.internal.R import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestableContext +import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.asIcon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -63,7 +64,7 @@ class ModesTileDataInteractorTest : SysuiTestCase() { fun setUp() { context.orCreateTestableResources.apply { addOverride(MODES_DRAWABLE_ID, MODES_DRAWABLE) - addOverride(R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE) + addOverride(BEDTIME_DRAWABLE_ID, BEDTIME_DRAWABLE) } val customPackageContext = SysuiTestableContext(context) @@ -145,24 +146,24 @@ class ModesTileDataInteractorTest : SysuiTestCase() { // Tile starts with the generic Modes icon. runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) - assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID) // Add an inactive mode -> Still modes icon zenModeRepository.addMode(id = "Mode", active = false) runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) - assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID) // Add an active mode with a default icon: icon should be the mode icon, and the // iconResId is also populated, because we know it's a system icon. zenModeRepository.addMode( id = "Bedtime with default icon", type = AutomaticZenRule.TYPE_BEDTIME, - active = true + active = true, ) runCurrent() assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) - assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime) + assertThat(tileData?.icon!!.res).isEqualTo(BEDTIME_DRAWABLE_ID) // Add another, less-prioritized mode that has a *custom* icon: for now, icon should // remain the first mode icon @@ -177,20 +178,20 @@ class ModesTileDataInteractorTest : SysuiTestCase() { ) runCurrent() assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) - assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime) + assertThat(tileData?.icon!!.res).isEqualTo(BEDTIME_DRAWABLE_ID) // Deactivate more important mode: icon should be the less important, still active mode // And because it's a package-provided icon, iconResId is not populated. zenModeRepository.deactivateMode("Bedtime with default icon") runCurrent() assertThat(tileData?.icon).isEqualTo(CUSTOM_ICON) - assertThat(tileData?.iconResId).isNull() + assertThat(tileData?.icon!!.res).isNull() // Deactivate remaining mode: back to the default modes icon zenModeRepository.deactivateMode("Driving with custom icon") runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) - assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID) } @Test @@ -205,18 +206,18 @@ class ModesTileDataInteractorTest : SysuiTestCase() { runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) - assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID) // Activate a Mode -> Icon doesn't change. zenModeRepository.addMode(id = "Mode", active = true) runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) - assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID) zenModeRepository.deactivateMode(id = "Mode") runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) - assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID) } @EnableFlags(Flags.FLAG_MODES_UI) @@ -256,15 +257,17 @@ class ModesTileDataInteractorTest : SysuiTestCase() { val TEST_USER = UserHandle.of(1)!! const val CUSTOM_PACKAGE = "com.some.mode.owner.package" - val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes + const val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes const val CUSTOM_DRAWABLE_ID = 12345 + const val BEDTIME_DRAWABLE_ID = R.drawable.ic_zen_mode_type_bedtime + val MODES_DRAWABLE = TestStubDrawable("modes_icon") val BEDTIME_DRAWABLE = TestStubDrawable("bedtime") val CUSTOM_DRAWABLE = TestStubDrawable("custom") - val MODES_ICON = MODES_DRAWABLE.asIcon() - val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon() + val MODES_ICON = Icon.Loaded(MODES_DRAWABLE, null, MODES_DRAWABLE_ID) + val BEDTIME_ICON = Icon.Loaded(BEDTIME_DRAWABLE, null, BEDTIME_DRAWABLE_ID) val CUSTOM_ICON = CUSTOM_DRAWABLE.asIcon() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt index 88b00468573f..04e094f25f5d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt @@ -156,6 +156,10 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { } private fun modelOf(isActivated: Boolean, activeModeNames: List<String>): ModesTileModel { - return ModesTileModel(isActivated, activeModeNames, TestStubDrawable("icon").asIcon(), 123) + return ModesTileModel( + isActivated, + activeModeNames, + TestStubDrawable("icon").asIcon(res = 123), + ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt index 4e91d16bf1ec..d73044f6b479 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt @@ -99,18 +99,11 @@ class ModesTileMapperTest : SysuiTestCase() { @Test fun state_modelHasIconResId_includesIconResId() { - val icon = TestStubDrawable("res123").asIcon() - val model = - ModesTileModel( - isActivated = false, - activeModes = emptyList(), - icon = icon, - iconResId = 123, - ) + val icon = TestStubDrawable("res123").asIcon(res = 123) + val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon) val state = underTest.map(config, model) assertThat(state.icon).isEqualTo(icon) - assertThat(state.iconRes).isEqualTo(123) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt index 1457f533f5ec..7c853261aa1c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt @@ -289,8 +289,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { if (TextUtils.isEmpty(secondaryLabel)) label else TextUtils.concat(label, ", ", secondaryLabel) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt index 2ac3e081b8f4..b6caa22a3012 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt @@ -58,8 +58,11 @@ class NotesTileMapperTest : SysuiTestCase() { private fun createNotesTileState(): QSTileState = QSTileState( - Icon.Loaded(context.getDrawable(R.drawable.ic_qs_notes)!!, null), - R.drawable.ic_qs_notes, + Icon.Loaded( + context.getDrawable(R.drawable.ic_qs_notes)!!, + null, + R.drawable.ic_qs_notes, + ), context.getString(R.string.quick_settings_notes_label), QSTileState.ActivationState.INACTIVE, /* secondaryLabel= */ null, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt index 7782d2b279a8..5b39810e3477 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt @@ -66,11 +66,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { val outputState = mapper.map(config, inputModel) val expectedState = - createOneHandedModeTileState( - QSTileState.ActivationState.INACTIVE, - subtitleArray[1], - com.android.internal.R.drawable.ic_qs_one_handed_mode, - ) + createOneHandedModeTileState(QSTileState.ActivationState.INACTIVE, subtitleArray[1]) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -81,23 +77,21 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { val outputState = mapper.map(config, inputModel) val expectedState = - createOneHandedModeTileState( - QSTileState.ActivationState.ACTIVE, - subtitleArray[2], - com.android.internal.R.drawable.ic_qs_one_handed_mode, - ) + createOneHandedModeTileState(QSTileState.ActivationState.ACTIVE, subtitleArray[2]) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } private fun createOneHandedModeTileState( activationState: QSTileState.ActivationState, secondaryLabel: String, - iconRes: Int, ): QSTileState { val label = context.getString(R.string.quick_settings_onehanded_label) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded( + context.getDrawable(com.android.internal.R.drawable.ic_qs_one_handed_mode)!!, + null, + com.android.internal.R.drawable.ic_qs_one_handed_mode, + ), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt index ed33250a3392..c572ff60b61a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt @@ -93,8 +93,8 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() { Icon.Loaded( context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!, null, + com.android.systemui.res.R.drawable.ic_qr_code_scanner, ), - com.android.systemui.res.R.drawable.ic_qr_code_scanner, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt index 85111fd07663..00017f9059de 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt @@ -85,8 +85,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() { R.drawable.qs_extra_dim_icon_on else R.drawable.qs_extra_dim_icon_off return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, context.resources diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt index 53671ba38eb6..74010143166b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt @@ -180,8 +180,7 @@ class RotationLockTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.quick_settings_rotation_unlocked_label) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt index 9a450653aa8f..1fb5048dd4c9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -91,8 +91,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { else context.resources.getStringArray(R.array.tile_states_saver)[0] return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt index cd683c44a59c..363255695131 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt @@ -110,8 +110,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_screen_record_label) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt index c569403960d0..e4cd0e0ec215 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt @@ -146,8 +146,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { else context.getString(R.string.quick_settings_mic_label) return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt index 0d2ebe42b7ad..8f5f2d3e6689 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt @@ -69,8 +69,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { expandedAccessibilityClass: KClass<out View>? = Switch::class, ): QSTileState { return QSTileState( - Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes, + Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt index 86321ea04703..2c81f39a03ec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt @@ -109,8 +109,7 @@ class WorkModeTileMapperTest : SysuiTestCase() { val label = testLabel val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status return QSTileState( - icon = Icon.Loaded(context.getDrawable(iconRes)!!, null), - iconRes = iconRes, + icon = Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes), label = label, activationState = activationState, secondaryLabel = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 165e943a0cc0..40f13bbbf908 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -16,10 +16,12 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -71,6 +73,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_noNotifs_empty() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -81,6 +84,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_notifMissingStatusBarChipIconView_empty() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -99,6 +103,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onePromotedNotif_statusBarIconViewMatches() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -122,6 +127,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onePromotedNotif_connectedDisplaysFlagEnabled_statusBarIconMatches() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -145,6 +151,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onePromotedNotif_colorMatches() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -175,6 +182,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onlyForPromotedNotifs() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -208,6 +216,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_connectedDisplaysFlagEnabled_onlyForPromotedNotifs() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -242,6 +251,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_hasShortCriticalText_usesTextInsteadOfTime() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -272,6 +282,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_noTime_isIconOnly() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -294,6 +305,36 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) + fun chips_basicTime_hiddenIfAutomaticallyPromoted() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + assertThat(latest!![0]) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + } + + @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_basicTime_isShortTimeDelta() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -322,6 +363,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_countUpTime_isTimer() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -349,6 +391,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_countDownTime_isTimer() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -376,6 +419,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_noHeadsUp_showsTime() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -407,6 +451,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_hasHeadsUpByUser_onlyShowsIcon() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -442,6 +487,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_clickingChipNotifiesInteractor() = kosmos.runTest { val latest by collectLastValue(underTest.chips) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt new file mode 100644 index 000000000000..14787e169979 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.chips.notification.shared.StatusBarPopupChips +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnableFlags(StatusBarPopupChips.FLAG_NAME) +@RunWith(AndroidJUnit4::class) +class StatusBarPopupChipsViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.statusBarPopupChipsViewModel + + @Test + fun popupChips_allHidden_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.popupChips) + assertThat(latest).isEmpty() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index cdc8bc1e6cbb..a49a66fe26b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -16,12 +16,18 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -36,7 +42,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.Person; import android.content.Context; +import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.CancellationSignal; import android.os.Handler; @@ -66,6 +74,7 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder; @@ -155,8 +164,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { @Test public void testIncreasedHeadsUpBeingUsed() { - BindParams params = new BindParams(); - params.usesIncreasedHeadsUpHeight = true; + BindParams params = new BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true, + REDACTION_TYPE_NONE); Notification.Builder builder = spy(mBuilder); mNotificationInflater.inflateNotificationViews( mRow.getEntry(), @@ -166,14 +175,15 @@ public class NotificationContentInflaterTest extends SysuiTestCase { FLAG_CONTENT_VIEW_ALL, builder, mContext, + mContext, mSmartReplyStateInflater); verify(builder).createHeadsUpContentView(true); } @Test public void testIncreasedHeightBeingUsed() { - BindParams params = new BindParams(); - params.usesIncreasedHeight = true; + BindParams params = new BindParams(false, /* usesIncreasedHeight */ true, false, + REDACTION_TYPE_NONE); Notification.Builder builder = spy(mBuilder); mNotificationInflater.inflateNotificationViews( mRow.getEntry(), @@ -183,6 +193,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { FLAG_CONTENT_VIEW_ALL, builder, mContext, + mContext, mSmartReplyStateInflater); verify(builder).createContentView(true); } @@ -207,7 +218,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mRow.getEntry().getSbn().getNotification().contentView = new RemoteViews(mContext.getPackageName(), com.android.systemui.res.R.layout.status_bar); inflateAndWait(true /* expectingException */, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, - mRow); + REDACTION_TYPE_NONE, mRow); assertTrue(mRow.getPrivateLayout().getChildCount() == 0); verify(mRow, times(0)).onNotificationUpdated(); } @@ -227,7 +238,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_ALL, - new BindParams(), + new BindParams(false, false, false, REDACTION_TYPE_NONE), false /* forceInflate */, null /* callback */); Assert.assertNull(mRow.getEntry().getRunningTask()); @@ -287,7 +298,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mBuilder.setCustomContentView(new RemoteViews(getContext().getPackageName(), R.layout.custom_view_dark)); RemoteViews decoratedMediaView = mBuilder.createContentView(); - Assert.assertFalse("The decorated media style doesn't allow a view to be reapplied!", + assertFalse("The decorated media style doesn't allow a view to be reapplied!", NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView)); } @@ -385,7 +396,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mRow.getPrivateLayout().removeAllViews(); mRow.getEntry().getSbn().getNotification().contentView = new RemoteViews(mContext.getPackageName(), R.layout.invalid_notification_height); - inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); + inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, REDACTION_TYPE_NONE, + mRow); assertEquals(0, mRow.getPrivateLayout().getChildCount()); verify(mRow, times(0)).onNotificationUpdated(); } @@ -455,16 +467,88 @@ public class NotificationContentInflaterTest extends SysuiTestCase { assertNull(mRow.getEntry().getPromotedNotificationContentModel()); } + @Test + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + public void testSensitiveContentPublicView_messageStyle() throws Exception { + String displayName = "Display Name"; + String messageText = "Message Text"; + String contentText = "Content Text"; + Icon personIcon = Icon.createWithResource(mContext, + com.android.systemui.res.R.drawable.ic_person); + Person testPerson = new Person.Builder() + .setName(displayName) + .setIcon(personIcon) + .build(); + Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(testPerson); + messagingStyle.addMessage(new Notification.MessagingStyle.Message(messageText, + System.currentTimeMillis(), testPerson)); + messagingStyle.setConversationType(Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL); + messagingStyle.setShortcutIcon(personIcon); + Notification messageNotif = new Notification.Builder(mContext).setSmallIcon( + com.android.systemui.res.R.drawable.ic_person).setStyle(messagingStyle).build(); + ExpandableNotificationRow row = mHelper.createRow(messageNotif); + inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, row); + NotificationContentView publicView = row.getPublicLayout(); + assertNotNull(publicView); + // The display name should be included, but not the content or message text + assertFalse(hasText(publicView, messageText)); + assertFalse(hasText(publicView, contentText)); + assertTrue(hasText(publicView, displayName)); + } + + @Test + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + public void testSensitiveContentPublicView_nonMessageStyle() throws Exception { + String contentTitle = "Content Title"; + String contentText = "Content Text"; + Notification notif = new Notification.Builder(mContext).setSmallIcon( + com.android.systemui.res.R.drawable.ic_person) + .setContentTitle(contentTitle) + .setContentText(contentText) + .build(); + ExpandableNotificationRow row = mHelper.createRow(notif); + inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, row); + NotificationContentView publicView = row.getPublicLayout(); + assertNotNull(publicView); + assertFalse(hasText(publicView, contentText)); + assertTrue(hasText(publicView, contentTitle)); + + // The standard public view should not use the content title or text + inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_PUBLIC, row); + publicView = row.getPublicLayout(); + assertFalse(hasText(publicView, contentText)); + assertFalse(hasText(publicView, contentTitle)); + } + + private static boolean hasText(ViewGroup parent, CharSequence text) { + for (int i = 0; i < parent.getChildCount(); i++) { + View child = parent.getChildAt(i); + if (child instanceof ViewGroup) { + if (hasText((ViewGroup) child, text)) { + return true; + } + } else if (child instanceof TextView) { + return ((TextView) child).getText().toString().contains(text); + } + } + return false; + } + private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) throws Exception { - inflateAndWait(false /* expectingException */, inflater, contentToInflate, row); + inflateAndWait(false /* expectingException */, inflater, contentToInflate, + REDACTION_TYPE_NONE, row); } private static void inflateAndWait(boolean expectingException, NotificationContentInflater inflater, @InflationFlag int contentToInflate, + @RedactionType int redactionType, ExpandableNotificationRow row) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); final ExceptionHolder exceptionHolder = new ExceptionHolder(); @@ -492,7 +576,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { row.getEntry(), row, contentToInflate, - new BindParams(), + new BindParams(false, false, false, redactionType), false /* forceInflate */, callback /* callback */); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt index 9fb72fba4d71..f25ba2c93c65 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row import android.app.Notification import android.app.Person import android.content.Context +import android.graphics.drawable.Icon import android.os.AsyncTask import android.os.Build import android.os.CancellationSignal @@ -34,6 +35,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT +import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -45,6 +50,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag @@ -110,6 +116,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { } } private val promotedNotificationContentExtractor = FakePromotedNotificationContentExtractor() + private val conversationNotificationProcessor: ConversationNotificationProcessor = mock() @Before fun setUp() { @@ -126,7 +133,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { NotificationRowContentBinderImpl( cache, mock(), - mock<ConversationNotificationProcessor>(), + conversationNotificationProcessor, mock(), smartReplyStateInflater, layoutInflaterFactoryProvider, @@ -138,14 +145,14 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testIncreasedHeadsUpBeingUsed() { - val params = BindParams() - params.usesIncreasedHeadsUpHeight = true + val params = + BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true, REDACTION_TYPE_NONE) val builder = spy(builder) notificationInflater.inflateNotificationViews( row.entry, row, params, - true /* inflateSynchronously */, + true, /* inflateSynchronously */ FLAG_CONTENT_VIEW_ALL, builder, mContext, @@ -157,14 +164,13 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testIncreasedHeightBeingUsed() { - val params = BindParams() - params.usesIncreasedHeight = true + val params = BindParams(false, /* usesIncreasedHeight */ true, false, REDACTION_TYPE_NONE) val builder = spy(builder) notificationInflater.inflateNotificationViews( row.entry, row, params, - true /* inflateSynchronously */, + true, /* inflateSynchronously */ FLAG_CONTENT_VIEW_ALL, builder, mContext, @@ -193,15 +199,18 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { row.entry.sbn.notification.contentView = RemoteViews(mContext.packageName, R.layout.status_bar) inflateAndWait( - true /* expectingException */, + true, /* expectingException */ notificationInflater, FLAG_CONTENT_VIEW_ALL, + REDACTION_TYPE_NONE, row, ) Assert.assertTrue(row.privateLayout.childCount == 0) verify(row, times(0)).onNotificationUpdated() } + @Test fun testInflationOfSensitiveContentPublicView() {} + @Test fun testAsyncTaskRemoved() { row.entry.abortTask() @@ -217,8 +226,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { row.entry, row, FLAG_CONTENT_VIEW_ALL, - BindParams(), - false /* forceInflate */, + BindParams(false, false, false, REDACTION_TYPE_NONE), + false, /* forceInflate */ null, /* callback */ ) Assert.assertNull(row.entry.runningTask) @@ -431,7 +440,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { mContext.packageName, com.android.systemui.tests.R.layout.invalid_notification_height, ) - inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, row) + inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, REDACTION_TYPE_NONE, row) Assert.assertEquals(0, row.privateLayout.childCount.toLong()) verify(row, times(0)).onNotificationUpdated() } @@ -440,7 +449,13 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testInflatePublicSingleLineView() { row.publicLayout.removeAllViews() - inflateAndWait(false, notificationInflater, FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, row) + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, + REDACTION_TYPE_NONE, + row, + ) Assert.assertNotNull(row.publicLayout.mSingleLineView) Assert.assertTrue(row.publicLayout.mSingleLineView is HybridNotificationView) } @@ -448,12 +463,15 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testInflatePublicSingleLineConversationView() { val testPerson = Person.Builder().setName("Person").build() + val style = Notification.MessagingStyle(testPerson) val messagingBuilder = Notification.Builder(mContext, "no-id") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text") - .setStyle(Notification.MessagingStyle(testPerson)) + .setStyle(style) + whenever(conversationNotificationProcessor.processNotification(any(), any(), any())) + .thenReturn(style) val messagingRow = spy(testHelper.createRow(messagingBuilder.build())) messagingRow.publicLayout.removeAllViews() @@ -461,6 +479,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { false, notificationInflater, FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, + REDACTION_TYPE_NONE, messagingRow, ) Assert.assertNotNull(messagingRow.publicLayout.mSingleLineView) @@ -530,6 +549,80 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { Assert.assertNull(row.entry.promotedNotificationContentModel) } + @Test + @Throws(java.lang.Exception::class) + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + fun testSensitiveContentPublicView_messageStyle() { + val displayName = "Display Name" + val messageText = "Message Text" + val contentText = "Content Text" + val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person) + val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build() + val messagingStyle = Notification.MessagingStyle(testPerson) + messagingStyle.addMessage( + Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson) + ) + messagingStyle.setConversationType(Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL) + messagingStyle.setShortcutIcon(personIcon) + val messageNotif = + Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_person) + .setStyle(messagingStyle) + .build() + val newRow: ExpandableNotificationRow = testHelper.createRow(messageNotif) + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, + newRow, + ) + // The display name should be included, but not the content or message text + val publicView = newRow.publicLayout + Assert.assertNotNull(publicView) + Assert.assertFalse(hasText(publicView, messageText)) + Assert.assertFalse(hasText(publicView, contentText)) + Assert.assertTrue(hasText(publicView, displayName)) + } + + @Test + @Throws(java.lang.Exception::class) + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + fun testSensitiveContentPublicView_nonMessageStyle() { + val contentTitle = "Content Title" + val contentText = "Content Text" + val notif = + Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_person) + .setContentTitle(contentTitle) + .setContentText(contentText) + .build() + val newRow: ExpandableNotificationRow = testHelper.createRow(notif) + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, + newRow, + ) + var publicView = newRow.publicLayout + Assert.assertNotNull(publicView) + Assert.assertFalse(hasText(publicView, contentText)) + Assert.assertTrue(hasText(publicView, contentTitle)) + + // The standard public view should not use the content title or text + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_PUBLIC, + newRow, + ) + publicView = newRow.publicLayout + Assert.assertFalse(hasText(publicView, contentText)) + Assert.assertFalse(hasText(publicView, contentTitle)) + } + private class ExceptionHolder { var exception: Exception? = null } @@ -568,13 +661,20 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @InflationFlag contentToInflate: Int, row: ExpandableNotificationRow, ) { - inflateAndWait(false /* expectingException */, inflater, contentToInflate, row) + inflateAndWait( + false /* expectingException */, + inflater, + contentToInflate, + REDACTION_TYPE_NONE, + row, + ) } private fun inflateAndWait( expectingException: Boolean, inflater: NotificationRowContentBinderImpl, @InflationFlag contentToInflate: Int, + @RedactionType redactionType: Int, row: ExpandableNotificationRow, ) { val countDownLatch = CountDownLatch(1) @@ -603,12 +703,26 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { row.entry, row, contentToInflate, - BindParams(), - false /* forceInflate */, + BindParams(false, false, false, redactionType), + false, /* forceInflate */ callback, /* callback */ ) Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)) exceptionHolder.exception?.let { throw it } } + + fun hasText(parent: ViewGroup, text: CharSequence): Boolean { + for (i in 0 until parent.childCount) { + val child = parent.getChildAt(i) + if (child is ViewGroup) { + if (hasText(child, text)) { + return true + } + } else if (child is TextView) { + return child.text.toString().contains(text) + } + } + return false + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index f48fd3c998b1..6bdd86efa8c0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -241,7 +241,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S shadeTestUtil.setSplitShade(true) val horizontalPosition = checkNotNull(dimens).horizontalPosition - assertIs<HorizontalPosition.FloatAtEnd>(horizontalPosition) + assertIs<HorizontalPosition.FloatAtStart>(horizontalPosition) assertThat(horizontalPosition.width).isEqualTo(200) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt index b560c591af1e..1ee8005fb7ab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt @@ -20,7 +20,9 @@ import android.view.WindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any @@ -31,7 +33,6 @@ import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith @@ -43,7 +44,7 @@ import org.mockito.Mockito.verify @RunWithLooper(setAsMainLooper = true) class SystemUIBottomSheetDialogTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val configurationController = mock<ConfigurationController>() private val config = mock<Configuration>() private val delegate = mock<DialogDelegate<Dialog>>() @@ -67,21 +68,17 @@ class SystemUIBottomSheetDialogTest : SysuiTestCase() { @Test fun onStart_registersConfigCallback() { - kosmos.testScope.runTest { + kosmos.runTest { dialog.show() - runCurrent() - verify(configurationController).addCallback(any()) } } @Test fun onStop_unregisterConfigCallback() { - kosmos.testScope.runTest { + kosmos.runTest { dialog.show() - runCurrent() dialog.dismiss() - runCurrent() verify(configurationController).removeCallback(any()) } @@ -89,14 +86,12 @@ class SystemUIBottomSheetDialogTest : SysuiTestCase() { @Test fun onConfigurationChanged_calledInDelegate() { - kosmos.testScope.runTest { + kosmos.runTest { dialog.show() - runCurrent() val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() verify(configurationController).addCallback(capture(captor)) captor.value.onConfigChanged(config) - runCurrent() verify(delegate).onConfigurationChanged(any(), any()) } diff --git a/packages/SystemUI/res/drawable/audio_bars_idle.xml b/packages/SystemUI/res/drawable/audio_bars_idle.xml new file mode 100644 index 000000000000..92a24755fece --- /dev/null +++ b/packages/SystemUI/res/drawable/audio_bars_idle.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="168dp" + android:height="168dp" + android:viewportWidth="168" + android:viewportHeight="168"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_2_G" + android:translateX="121.161" + android:translateY="83.911"> + <path + android:name="_R_G_L_2_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + <group + android:name="_R_G_L_1_G" + android:translateX="102.911" + android:translateY="83.911"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="139.661" + android:translateY="83.911"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + </group> + <group android:name="time_group" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index a3bad8f012ac..5ccedeafcb59 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -56,8 +56,8 @@ android:layout_marginTop="@dimen/volume_dialog_components_spacing" android:background="@drawable/ripple_drawable_20dp" android:contentDescription="@string/accessibility_volume_settings" + android:scaleType="centerInside" android:soundEffectsEnabled="false" - android:src="@drawable/horizontal_ellipsis" android:tint="@androidprv:color/materialColorPrimary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" diff --git a/packages/SystemUI/res/raw/audio_bars_in.json b/packages/SystemUI/res/raw/audio_bars_in.json new file mode 100644 index 000000000000..c90a59c47d64 --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_in.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":18,"w":168,"h":168,"nm":"audio_bars_in","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/audio_bars_out.json b/packages/SystemUI/res/raw/audio_bars_out.json new file mode 100644 index 000000000000..5eab65e057ab --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_out.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":31,"w":168,"h":168,"nm":"audio_bars_out","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/audio_bars_playing.json b/packages/SystemUI/res/raw/audio_bars_playing.json new file mode 100644 index 000000000000..6ee8e1915f36 --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_playing.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":121,"w":168,"h":168,"nm":"audio_bars_playing","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":38,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":70,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":102,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":32,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":29,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":59,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":91,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":54,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":86,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0}],"markers":[{"tm":60,"cm":"1","dr":0}]}
\ No newline at end of file diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index ab0f788dbb13..ec24c3df36a8 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -23,6 +23,9 @@ <!-- The maximum number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">4</integer> + <!-- The number of columns in the Split Shade QuickSettings --> + <integer name="quick_settings_split_shade_num_columns">6</integer> + <!-- Use collapsed layout for media player in landscape QQS --> <bool name="config_quickSettingsMediaLandscapeCollapsed">false</bool> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index fc536bdb126b..6f13d637d5c5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -20,6 +20,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; +import static com.android.systemui.Flags.glanceableHubBackAction; import static com.android.systemui.shared.Flags.shadeAllowBackGesture; import android.annotation.LongDef; @@ -352,6 +353,10 @@ public class QuickStepContract { } // Disable back gesture on the hub, but not when the shade is showing. if ((sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { + // Allow back gesture on Glanceable Hub with back action support. + if (glanceableHubBackAction()) { + return false; + } // Use QS expanded signal as the notification panel is always considered visible // expanded when on the lock screen and when opening hub over lock screen. This does // mean that back gesture is disabled when opening shade over hub while in portrait diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 1083136b570a..acfa08643b63 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -26,11 +26,9 @@ import android.hardware.display.DisplayManager; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.os.Trace; -import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.Display; -import android.view.DisplayAddress; import android.view.DisplayInfo; import android.view.View; import android.view.WindowManager; @@ -58,6 +56,9 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Provider; +/** + * Manages Keyguard Presentations for non-primary display(s). + */ @SysUISingleton public class KeyguardDisplayManager { protected static final String TAG = "KeyguardDisplayManager"; @@ -170,14 +171,17 @@ public class KeyguardDisplayManager { } return false; } - if (mKeyguardStateController.isOccluded() - && mDeviceStateHelper.isConcurrentDisplayActive(display)) { + + final boolean deviceStateOccludesKeyguard = + mDeviceStateHelper.isConcurrentDisplayActive(display) + || mDeviceStateHelper.isRearDisplayOuterDefaultActive(display); + if (mKeyguardStateController.isOccluded() && deviceStateOccludesKeyguard) { if (DEBUG) { // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the // Keyguard state becomes "occluded". In this case, we should not show the // KeyguardPresentation, since the activity is presenting content onto the // non-default display. - Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent" + Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent or rear" + " display is active"); } return false; @@ -326,44 +330,45 @@ public class KeyguardDisplayManager { public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback { @Nullable - private final DisplayAddress.Physical mRearDisplayPhysicalAddress; - - // TODO(b/271317597): These device states should be defined in DeviceStateManager - private final int mConcurrentState; - private boolean mIsInConcurrentDisplayState; + private DeviceState mDeviceState; @Inject DeviceStateHelper( - @ShadeDisplayAware Context context, DeviceStateManager deviceStateManager, @Main Executor mainExecutor) { - - final String rearDisplayPhysicalAddress = context.getResources().getString( - com.android.internal.R.string.config_rearDisplayPhysicalAddress); - if (TextUtils.isEmpty(rearDisplayPhysicalAddress)) { - mRearDisplayPhysicalAddress = null; - } else { - mRearDisplayPhysicalAddress = DisplayAddress - .fromPhysicalDisplayId(Long.parseLong(rearDisplayPhysicalAddress)); - } - - mConcurrentState = context.getResources().getInteger( - com.android.internal.R.integer.config_deviceStateConcurrentRearDisplay); deviceStateManager.registerCallback(mainExecutor, this); } @Override 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.getIdentifier() == mConcurrentState; + // When dual display or rear display mode 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. + mDeviceState = state; + } + + /** + * @return true if the device is in Dual Display mode, and the specified display is the + * rear facing (outer) display. + */ + boolean isConcurrentDisplayActive(@NonNull Display display) { + return mDeviceState != null + && mDeviceState.hasProperty( + DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT) + && (display.getFlags() & Display.FLAG_REAR) != 0; } - boolean isConcurrentDisplayActive(Display display) { - return mIsInConcurrentDisplayState - && mRearDisplayPhysicalAddress != null - && mRearDisplayPhysicalAddress.equals(display.getAddress()); + /** + * @return true if the device is the updated Rear Display mode, and the specified display is + * the inner display. See {@link DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT}. + * Note that in this state, the outer display is the default display, while the inner + * display is the "rear" display. + */ + boolean isRearDisplayOuterDefaultActive(@NonNull Display display) { + return mDeviceState != null + && mDeviceState.hasProperty( + DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT) + && (display.getFlags() & Display.FLAG_REAR) != 0; } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt index 07bd813c2420..40a86dc3713e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt @@ -19,13 +19,12 @@ package com.android.keyguard import android.content.Context import android.view.View import com.android.systemui.customization.R as customR -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R -import com.android.systemui.shared.R as sharedR import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shared.R as sharedR import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START @@ -55,16 +54,17 @@ constructor( var statusViewCentered = false private val filterKeyguardAndSplitShadeOnly: () -> Boolean = { - statusBarStateController.getState() == KEYGUARD && !statusViewCentered } + statusBarStateController.getState() == KEYGUARD && !statusViewCentered + } private val filterKeyguard: () -> Boolean = { statusBarStateController.getState() == KEYGUARD } private val translateAnimator by lazy { - val smartSpaceViews = if (MigrateClocksToBlueprint.isEnabled) { - // Use scrollX instead of translationX as translation is already set by [AodBurnInLayer] - val scrollXTranslation = { view: View, translation: Float -> - view.scrollX = -translation.toInt() - } + // Use scrollX instead of translationX as translation is already set by [AodBurnInLayer] + val scrollXTranslation = { view: View, translation: Float -> + view.scrollX = -translation.toInt() + } + val smartSpaceViews = setOf( ViewIdToTranslate( viewId = sharedR.id.date_smartspace_view, @@ -83,18 +83,8 @@ constructor( direction = START, shouldBeAnimated = filterKeyguard, translateFunc = scrollXTranslation, - ) + ), ) - } else { - setOf(ViewIdToTranslate( - viewId = R.id.keyguard_status_area, - direction = START, - shouldBeAnimated = filterKeyguard, - translateFunc = { view, value -> - (view as? KeyguardStatusAreaView)?.translateXFromUnfold = value - } - )) - } UnfoldConstantTranslateAnimator( viewsIdToTranslate = @@ -102,39 +92,39 @@ constructor( ViewIdToTranslate( viewId = customR.id.lockscreen_clock_view_large, direction = START, - shouldBeAnimated = filterKeyguardAndSplitShadeOnly + shouldBeAnimated = filterKeyguardAndSplitShadeOnly, ), ViewIdToTranslate( viewId = customR.id.lockscreen_clock_view, direction = START, - shouldBeAnimated = filterKeyguard + shouldBeAnimated = filterKeyguard, ), ViewIdToTranslate( viewId = R.id.notification_stack_scroller, direction = END, - shouldBeAnimated = filterKeyguardAndSplitShadeOnly - ) + shouldBeAnimated = filterKeyguardAndSplitShadeOnly, + ), ) + smartSpaceViews, - progressProvider = unfoldProgressProvider + progressProvider = unfoldProgressProvider, ) } private val shortcutButtonsAnimator by lazy { UnfoldConstantTranslateAnimator( viewsIdToTranslate = - setOf( - ViewIdToTranslate( - viewId = R.id.start_button, - direction = START, - shouldBeAnimated = filterKeyguard + setOf( + ViewIdToTranslate( + viewId = R.id.start_button, + direction = START, + shouldBeAnimated = filterKeyguard, + ), + ViewIdToTranslate( + viewId = R.id.end_button, + direction = END, + shouldBeAnimated = filterKeyguard, + ), ), - ViewIdToTranslate( - viewId = R.id.end_button, - direction = END, - shouldBeAnimated = filterKeyguard - ) - ), - progressProvider = unfoldProgressProvider + progressProvider = unfoldProgressProvider, ) } diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt index 232b62985ad0..47910f3d25bc 100644 --- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt @@ -21,8 +21,11 @@ import android.window.OnBackAnimationCallback import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import android.window.WindowOnBackInvokedDispatcher +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable +import com.android.systemui.Flags.glanceableHubBackAction import com.android.systemui.Flags.predictiveBackAnimateShade +import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -35,7 +38,6 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch /** Handles requests to go back either from a button or gesture. */ @SysUISingleton @@ -50,6 +52,7 @@ constructor( private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor, private val shadeBackActionInteractor: ShadeBackActionInteractor, private val qsController: QuickSettingsController, + private val communalBackActionInteractor: CommunalBackActionInteractor, ) : CoreStartable { private var isCallbackRegistered = false @@ -114,6 +117,12 @@ constructor( if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) { return true } + if (glanceableHubBackAction()) { + if (communalBackActionInteractor.canBeDismissed()) { + communalBackActionInteractor.onBackPressed() + return true + } + } if (shouldBackBeHandled()) { if (shadeBackActionInteractor.canBeCollapsed()) { // this is the Shade dismiss animation, so make sure QQS closes when it ends. diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt index aef5f1f422d1..e6f02457d320 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt @@ -21,14 +21,17 @@ import android.graphics.drawable.Drawable /** * Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference] - * [Icon.Resource] to a resource. + * [Icon.Resource] to a resource. In case of [Loaded], the resource ID [res] is optional. */ sealed class Icon { abstract val contentDescription: ContentDescription? - data class Loaded( + data class Loaded + @JvmOverloads + constructor( val drawable: Drawable, override val contentDescription: ContentDescription?, + @DrawableRes val res: Int? = null, ) : Icon() data class Resource( @@ -37,6 +40,11 @@ sealed class Icon { ) : Icon() } -/** Creates [Icon.Loaded] for a given drawable with an optional [contentDescription]. */ -fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon.Loaded = - Icon.Loaded(this, contentDescription) +/** + * Creates [Icon.Loaded] for a given drawable with an optional [contentDescription] and an optional + * [res]. + */ +fun Drawable.asIcon( + contentDescription: ContentDescription? = null, + @DrawableRes res: Int? = null, +): Icon.Loaded = Icon.Loaded(this, contentDescription, res) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt new file mode 100644 index 000000000000..2ccf96abff79 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import javax.inject.Inject + +/** + * {@link CommunalBackActionInteractor} is responsible for handling back gestures on the glanceable + * hub. When invoked SystemUI should navigate back to the lockscreen. + */ +@SysUISingleton +class CommunalBackActionInteractor +@Inject +constructor( + private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, + private val sceneInteractor: SceneInteractor, +) { + fun canBeDismissed(): Boolean { + return communalInteractor.isCommunalShowing.value + } + + fun onBackPressed() { + if (SceneContainerFlag.isEnabled) { + // TODO(b/384610333): Properly determine whether to go to dream or lockscreen on back. + sceneInteractor.changeScene( + toScene = Scenes.Lockscreen, + loggingReason = "CommunalBackActionInteractor", + ) + } else { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + loggingReason = "CommunalBackActionInteractor", + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index ea428698e476..947113da0e60 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -285,7 +285,7 @@ constructor( * use [isIdleOnCommunal]. */ // TODO(b/323215860): rename to something more appropriate after cleaning up usages - val isCommunalShowing: Flow<Boolean> = + val isCommunalShowing: StateFlow<Boolean> = flow { emit(SceneContainerFlag.isEnabled) } .flatMapLatest { sceneContainerEnabled -> if (sceneContainerEnabled) { @@ -304,10 +304,10 @@ constructor( columnName = "isCommunalShowing", initialValue = false, ) - .shareIn( + .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - replay = 1, + started = SharingStarted.Eagerly, + initialValue = false, ) /** diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 9ae106c3ab39..014c0db618e1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -267,6 +267,7 @@ public class FrameworkServicesModule { } @Provides + @Nullable @Singleton static VirtualDeviceManager provideVirtualDeviceManager(Context context) { return context.getSystemService(VirtualDeviceManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 571b37f43fd4..b272d65a8a11 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -54,6 +54,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.Flags; import com.android.systemui.ambient.touch.TouchHandler; import com.android.systemui.ambient.touch.TouchMonitor; import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent; @@ -210,6 +211,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mCommunalVisible = communalVisible; updateLifecycleStateLocked(); + updateGestureBlockingLocked(); }); } }; @@ -585,7 +587,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private void updateGestureBlockingLocked() { final boolean shouldBlock = mStarted && !mShadeExpanded && !mBouncerShowing - && !isDreamInPreviewMode(); + && !isDreamInPreviewMode() + && !(Flags.glanceableHubBackAction() && mCommunalVisible); if (shouldBlock) { mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index f549e64ca853..d0065c8b06c6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -39,7 +39,6 @@ import androidx.annotation.VisibleForTesting import androidx.core.math.MathUtils import com.android.app.animation.Interpolators import com.android.internal.R -import com.android.keyguard.KeyguardClockSwitchController import com.android.keyguard.KeyguardViewController import com.android.systemui.Flags.fasterUnlockTransition import com.android.systemui.dagger.SysUISingleton @@ -206,7 +205,7 @@ constructor( fun onUnlockAnimationFinished() {} } - /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */ + /** The SmartSpace view on the lockscreen. */ var lockscreenSmartspace: View? = null /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt index 74ee052f12b9..57f06fbd3bb5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt @@ -21,13 +21,13 @@ import android.app.StatusBarManager import android.app.admin.DevicePolicyManager import android.content.Context import android.content.pm.PackageManager -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.camera.CameraGestureHelper import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeDisplayAware import dagger.Lazy @@ -65,7 +65,7 @@ constructor( icon = Icon.Resource( R.drawable.ic_camera, - ContentDescription.Resource(R.string.accessibility_camera_button) + ContentDescription.Resource(R.string.accessibility_camera_button), ) ) } else { @@ -88,7 +88,7 @@ constructor( cameraGestureHelper .get() .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true) } private suspend fun isLaunchable(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt index e8d3bfac6361..1b8baf657948 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt @@ -210,16 +210,16 @@ constructor( ): KeyguardQuickAffordanceConfig.OnTriggeredResult { return if (ModesUi.isEnabled) { if (!isAvailable.value) { - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } else { val dnd = interactor.dndMode.value if (dnd == null) { Log.wtf(TAG, "Triggered DND but it's null!?") - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } if (dnd.isActive) { interactor.deactivateMode(dnd) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } else { if (interactor.shouldAskForZenDuration(dnd)) { // NOTE: The dialog handles turning on the mode itself. @@ -229,16 +229,16 @@ constructor( ) } else { interactor.activateMode(dnd) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } } } } else { when { - !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) zenMode != ZEN_MODE_OFF -> { controller.setZen(ZEN_MODE_OFF, null, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } settingsValue == ZEN_DURATION_PROMPT -> @@ -249,12 +249,12 @@ constructor( settingsValue == ZEN_DURATION_FOREVER -> { controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } else -> { controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt index 480ef5e19d8e..e2642a0964c1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -18,15 +18,14 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.policy.FlashlightController import javax.inject.Inject @@ -50,9 +49,9 @@ constructor( KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_flashlight_icon_on, - ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ContentDescription.Resource(R.string.quick_settings_flashlight_label), ), - ActivationState.Active + ActivationState.Active, ) } @@ -61,9 +60,9 @@ constructor( KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_flashlight_icon_off, - ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ContentDescription.Resource(R.string.quick_settings_flashlight_label), ), - ActivationState.Inactive + ActivationState.Inactive, ) } @@ -92,14 +91,14 @@ constructor( } else { FlashlightState.OffAvailable.toLockScreenState() }, - TAG + TAG, ) } override fun onFlashlightError() { trySendWithFailureLogging( FlashlightState.OffAvailable.toLockScreenState(), - TAG + TAG, ) } @@ -114,7 +113,7 @@ constructor( FlashlightState.OffAvailable.toLockScreenState() } }, - TAG + TAG, ) } } @@ -130,7 +129,7 @@ constructor( flashlightController.setFlashlight( flashlightController.isAvailable && !flashlightController.isEnabled ) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt index d335a1806a6d..06da281648a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt @@ -111,7 +111,7 @@ constructor( transitionKey = CommunalTransitionKeys.SimpleFade, ) } - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt index 1cf6183fec6c..ade65c38ff3c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -21,10 +21,10 @@ import android.app.AlertDialog import android.content.Context import android.content.Intent import android.net.Uri -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.res.R import kotlinx.coroutines.flow.Flow /** Defines interface that can act as data source for a single quick affordance model. */ @@ -71,7 +71,7 @@ interface KeyguardQuickAffordanceConfig { /** The picker shows the item for selecting this affordance as it normally would. */ data class Default( /** Optional [Intent] to use to start an activity to configure this affordance. */ - val configureIntent: Intent? = null, + val configureIntent: Intent? = null ) : PickerScreenState() /** @@ -134,34 +134,39 @@ interface KeyguardQuickAffordanceConfig { ) : LockScreenState() } - sealed class OnTriggeredResult { + sealed class OnTriggeredResult() { /** * Returning this as a result from the [onTriggered] method means that the implementation * has taken care of the action, the system will do nothing. + * + * @param[actionLaunched] Whether the implementation handled the action by launching a + * dialog or an activity. */ - object Handled : OnTriggeredResult() + data class Handled(val actionLaunched: Boolean) : OnTriggeredResult() /** * Returning this as a result from the [onTriggered] method means that the implementation * has _not_ taken care of the action and the system should start an activity using the * given [Intent]. */ - data class StartActivity( - val intent: Intent, - val canShowWhileLocked: Boolean, - ) : OnTriggeredResult() + data class StartActivity(val intent: Intent, val canShowWhileLocked: Boolean) : + OnTriggeredResult() /** * Returning this as a result from the [onTriggered] method means that the implementation * has _not_ taken care of the action and the system should show a Dialog using the given * [AlertDialog] and [Expandable]. */ - data class ShowDialog( - val dialog: AlertDialog, - val expandable: Expandable?, - ) : OnTriggeredResult() + data class ShowDialog(val dialog: AlertDialog, val expandable: Expandable?) : + OnTriggeredResult() } + /** + * Models an [OnTriggeredResult] that did or did not launch a dialog or activity for a given + * config key. + */ + data class LaunchingFromTriggeredResult(val launched: Boolean, val configKey: String) + companion object { /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt index 1358634a55f8..1c9bc9f39663 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt @@ -21,6 +21,7 @@ import android.content.Context import android.media.AudioManager import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext @SysUISingleton @@ -118,7 +118,7 @@ constructor( audioManager.ringerModeInternal = newRingerMode } } - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = @@ -140,11 +140,11 @@ constructor( .getSharedPreferences( MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME, Context.MODE_PRIVATE, - userTracker.userId + userTracker.userId, ) .getInt( LAST_NON_SILENT_RINGER_MODE_KEY, - ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE + ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index eafa1cea59f3..cb7702e090d0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -30,7 +30,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R @@ -72,21 +71,15 @@ constructor( override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { val hasCards = getPaymentCards(response.walletCards)?.isNotEmpty() == true - trySendWithFailureLogging( - hasCards, - TAG, - ) + trySendWithFailureLogging(hasCards, TAG) } override fun onWalletCardRetrievalError(error: GetWalletCardsError) { Log.e( TAG, - "Wallet card retrieval error, message: \"${error?.message}\"" - ) - trySendWithFailureLogging( - null, - TAG, + "Wallet card retrieval error, message: \"${error?.message}\"", ) + trySendWithFailureLogging(null, TAG) } } @@ -94,7 +87,7 @@ constructor( callback, QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE, - QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE + QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE, ) withContext(backgroundDispatcher) { @@ -107,7 +100,7 @@ constructor( walletController.unregisterWalletChangeObservers( QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE, - QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE + QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE, ) } } @@ -117,11 +110,7 @@ constructor( if (hasCards == null) { KeyguardQuickAffordanceConfig.LockScreenState.Hidden } else { - state( - isWalletAvailable(), - hasCards, - walletController.walletClient.tileIcon, - ) + state(isWalletAvailable(), hasCards, walletController.walletClient.tileIcon) } flowOf(state) } @@ -135,28 +124,28 @@ constructor( explanation = context.getString( R.string.wallet_quick_affordance_unavailable_install_the_app - ), + ) ) queryCards().isEmpty() -> KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( explanation = context.getString( R.string.wallet_quick_affordance_unavailable_configure_the_app - ), + ) ) else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() } } override fun onTriggered( - expandable: Expandable?, + expandable: Expandable? ): KeyguardQuickAffordanceConfig.OnTriggeredResult { walletController.startQuickAccessUiIntent( activityStarter, expandable?.activityTransitionController(), /* hasCard= */ true, ) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true) } private suspend fun queryCards(): List<WalletCard> { @@ -199,10 +188,8 @@ constructor( Icon.Loaded( drawable = tileIcon, contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), - ), + ContentDescription.Resource(res = R.string.accessibility_wallet_button), + ) ) } else { KeyguardQuickAffordanceConfig.LockScreenState.Hidden diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index ae55825c9842..9c2daf52c5df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -29,7 +29,6 @@ import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.dock.DockManager @@ -62,6 +61,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -101,6 +101,14 @@ constructor( val launchingAffordance: StateFlow<Boolean> = repository.get().launchingAffordance.asStateFlow() /** + * Whether a [KeyguardQuickAffordanceConfig.OnTriggeredResult] indicated that the system + * launched an activity or showed a dialog. + */ + private val _launchingFromTriggeredResult = + MutableStateFlow<KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult?>(null) + val launchingFromTriggeredResult = _launchingFromTriggeredResult.asStateFlow() + + /** * Whether the UI should use the long press gesture to activate quick affordances. * * If `false`, the UI goes back to using single taps. @@ -187,18 +195,45 @@ constructor( metricsLogger.logOnShortcutTriggered(slotId, configKey) when (val result = config.onTriggered(expandable)) { - is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> + is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> { + setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( + launched = true, + configKey, + ) + ) launchQuickAffordance( intent = result.intent, canShowWhileLocked = result.canShowWhileLocked, expandable = expandable, ) - is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit - is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> + } + is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> { + setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( + result.actionLaunched, + configKey, + ) + ) + } + is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> { + setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( + launched = true, + configKey, + ) + ) showDialog(result.dialog, result.expandable) + } } } + fun setLaunchingFromTriggeredResult( + launchingResult: KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult? + ) { + _launchingFromTriggeredResult.value = launchingResult + } + /** * Selects an affordance with the given ID on the slot with the given ID. * diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt index aa44b6d46289..382436cf9397 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor.scenetransition +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable @@ -38,7 +39,6 @@ import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import com.android.app.tracing.coroutines.launchTraced as launch /** * This class listens to scene framework scene transitions and manages keyguard transition framework @@ -111,7 +111,10 @@ constructor( if (currentTransitionId == null) return if (prevTransition !is ObservableTransitionState.Transition) return - if (idle.currentScene == prevTransition.toContent) { + if ( + idle.currentScene == prevTransition.toContent || + idle.currentOverlays.contains(prevTransition.toContent) + ) { finishCurrentTransition() } else { val targetState = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt index e7803c5e964c..a4a5ba691965 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt @@ -17,12 +17,23 @@ package com.android.systemui.keyguard.ui.binder import android.os.VibrationEffect +import com.android.systemui.Flags import kotlin.time.Duration.Companion.milliseconds object KeyguardBottomAreaVibrations { - val ShakeAnimationDuration = 300.milliseconds - const val ShakeAnimationCycles = 5f + val ShakeAnimationDuration = + if (Flags.msdlFeedback()) { + 285.milliseconds + } else { + 300.milliseconds + } + val ShakeAnimationCycles = + if (Flags.msdlFeedback()) { + 3f + } else { + 5f + } private const val SmallVibrationScale = 0.3f private const val BigVibrationScale = 0.6f @@ -32,7 +43,7 @@ object KeyguardBottomAreaVibrations { .apply { val vibrationDelayMs = (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2)) - .toInt() + .toInt() val vibrationCount = ShakeAnimationCycles.toInt() * 2 repeat(vibrationCount) { @@ -47,29 +58,13 @@ object KeyguardBottomAreaVibrations { val Activated = VibrationEffect.startComposition() - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_TICK, - BigVibrationScale, - 0, - ) - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, - 0.1f, - 0, - ) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, BigVibrationScale, 0) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.1f, 0) .compose() val Deactivated = VibrationEffect.startComposition() - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_TICK, - BigVibrationScale, - 0, - ) - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, - 0.1f, - 0, - ) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, BigVibrationScale, 0) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.1f, 0) .compose() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 8725cdd273df..8a2e3dd791c2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.drawable.Animatable2 +import android.os.VibrationEffect import android.util.Size import android.view.View import android.view.ViewGroup @@ -33,25 +34,27 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardQuickAffordancesLogger +import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.doOnEnd +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.MSDLPlayer import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** This is only for a SINGLE Quick affordance */ @SysUISingleton @@ -60,8 +63,9 @@ class KeyguardQuickAffordanceViewBinder constructor( private val falsingManager: FalsingManager?, private val vibratorHelper: VibratorHelper?, + private val msdlPlayer: MSDLPlayer, private val logger: KeyguardQuickAffordancesLogger, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, + private val hapticsViewModelFactory: KeyguardQuickAffordanceHapticViewModel.Factory, ) { private val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L @@ -88,6 +92,12 @@ constructor( ): Binding { val button = view as ImageView val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) + val hapticsViewModel = + if (Flags.msdlFeedback()) { + hapticsViewModelFactory.create(viewModel) + } else { + null + } val disposableHandle = view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -98,15 +108,12 @@ constructor( viewModel = buttonModel, messageDisplayer = messageDisplayer, ) + hapticsViewModel?.updateActivatedHistory(buttonModel.isActivated) } } launch { - updateButtonAlpha( - view = button, - viewModel = viewModel, - alphaFlow = alpha, - ) + updateButtonAlpha(view = button, viewModel = viewModel, alphaFlow = alpha) } launch { @@ -117,6 +124,32 @@ constructor( } } } + + if (Flags.msdlFeedback()) { + launch { + hapticsViewModel + ?.quickAffordanceHapticState + ?.filter { + it != + KeyguardQuickAffordanceHapticViewModel.HapticState + .NO_HAPTICS + } + ?.collect { state -> + when (state) { + KeyguardQuickAffordanceHapticViewModel.HapticState + .TOGGLE_ON -> msdlPlayer.playToken(MSDLToken.SWITCH_ON) + KeyguardQuickAffordanceHapticViewModel.HapticState + .TOGGLE_OFF -> + msdlPlayer.playToken(MSDLToken.SWITCH_OFF) + KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH -> + msdlPlayer.playToken(MSDLToken.LONG_PRESS) + KeyguardQuickAffordanceHapticViewModel.HapticState + .NO_HAPTICS -> Unit + } + hapticsViewModel.resetLaunchingFromTriggeredResult() + } + } + } } } @@ -178,7 +211,7 @@ constructor( com.android.internal.R.color.materialColorOnPrimaryFixed } else { com.android.internal.R.color.materialColorOnSurface - }, + } ) ) @@ -221,12 +254,7 @@ constructor( .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude) .toFloat() val shakeAnimator = - ObjectAnimator.ofFloat( - view, - "translationX", - -amplitude / 2, - amplitude / 2, - ) + ObjectAnimator.ofFloat(view, "translationX", -amplitude / 2, amplitude / 2) shakeAnimator.duration = KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds shakeAnimator.interpolator = @@ -234,11 +262,17 @@ constructor( shakeAnimator.doOnEnd { view.translationX = 0f } shakeAnimator.start() - vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) + vibratorHelper?.playFeedback(KeyguardBottomAreaVibrations.Shake, msdlPlayer) logger.logQuickAffordanceTapped(viewModel.configKey) } view.onLongClickListener = - OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) + OnLongClickListener( + falsingManager, + viewModel, + vibratorHelper, + onTouchListener, + msdlPlayer, + ) } else { view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) } @@ -268,7 +302,7 @@ constructor( Size( view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), - ), + ) ) } @@ -297,7 +331,8 @@ constructor( private val falsingManager: FalsingManager?, private val viewModel: KeyguardQuickAffordanceViewModel, private val vibratorHelper: VibratorHelper?, - private val onTouchListener: KeyguardQuickAffordanceOnTouchListener + private val onTouchListener: KeyguardQuickAffordanceOnTouchListener, + private val msdlPlayer: MSDLPlayer, ) : View.OnLongClickListener { override fun onLongClick(view: View): Boolean { if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { @@ -312,12 +347,13 @@ constructor( slotId = viewModel.slotId, ) ) - vibratorHelper?.vibrate( + vibratorHelper?.playFeedback( if (viewModel.isActivated) { KeyguardBottomAreaVibrations.Activated } else { KeyguardBottomAreaVibrations.Deactivated - } + }, + msdlPlayer, ) } @@ -328,7 +364,15 @@ constructor( override fun onLongClickUseDefaultHapticFeedback(view: View) = false } - private data class ConfigurationBasedDimensions( - val buttonSizePx: Size, - ) + private data class ConfigurationBasedDimensions(val buttonSizePx: Size) +} + +private fun VibratorHelper.playFeedback(effect: VibrationEffect, msdlPlayer: MSDLPlayer) { + if (!Flags.msdlFeedback()) { + vibrate(effect) + } else { + if (effect == KeyguardBottomAreaVibrations.Shake) { + msdlPlayer.playToken(MSDLToken.FAILURE) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index a2ce4ec5ce9b..6d270b219c81 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -127,21 +127,18 @@ object KeyguardRootViewBinder { if (Flags.nonTouchscreenDevicesBypassFalsing()) { if ( event.action == MotionEvent.ACTION_DOWN && - event.buttonState == MotionEvent.BUTTON_PRIMARY && - !event.isTouchscreenSource() + event.buttonState == MotionEvent.BUTTON_PRIMARY && + !event.isTouchscreenSource() ) { consumed = true } else if ( - event.action == MotionEvent.ACTION_UP && - !event.isTouchscreenSource() + event.action == MotionEvent.ACTION_UP && !event.isTouchscreenSource() ) { statusBarKeyguardViewManager?.showBouncer(true) consumed = true } } - viewModel.setRootViewLastTapPosition( - Point(event.x.toInt(), event.y.toInt()) - ) + viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt())) } consumed } @@ -172,7 +169,6 @@ object KeyguardRootViewBinder { launch("$TAG#alpha") { viewModel.alpha(viewState).collect { alpha -> view.alpha = alpha - childViews[statusViewId]?.alpha = alpha childViews[burnInLayerId]?.alpha = alpha } } @@ -253,18 +249,6 @@ object KeyguardRootViewBinder { } launch { - viewModel.burnInLayerAlpha.collect { alpha -> - childViews[statusViewId]?.alpha = alpha - } - } - - launch { - viewModel.lockscreenStateAlpha(viewState).collect { alpha -> - childViews[statusViewId]?.alpha = alpha - } - } - - launch { viewModel.scale.collect { scaleViewModel -> if (scaleViewModel.scaleClockOnly) { // For clocks except weather clock, we have scale transition besides @@ -553,7 +537,6 @@ object KeyguardRootViewBinder { return device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == true } - private val statusViewId = R.id.keyguard_status_view private val burnInLayerId = R.id.burn_in_layer private val aodNotificationIconContainerId = R.id.aod_notification_icon_container private val largeClockId = customR.id.lockscreen_clock_view_large diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 090b65922d2d..6fb31c0e4191 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -48,19 +48,16 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isInvisible -import com.android.internal.policy.SystemBarUtils import com.android.keyguard.ClockEventController -import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel -import com.android.systemui.coroutines.newTracingContext +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder @@ -80,7 +77,6 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.clocks.ClockRegistry -import com.android.systemui.shared.clocks.DefaultClockController import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants @@ -91,18 +87,13 @@ import com.android.systemui.util.settings.SecureSettings import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.json.JSONException import org.json.JSONObject -import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.systemui.customization.R as customR /** Renders the preview of the lock screen. */ class KeyguardPreviewRenderer @@ -110,7 +101,6 @@ class KeyguardPreviewRenderer @AssistedInject constructor( @Application private val context: Context, - @Application applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, @Main private val mainHandler: Handler, @Background private val backgroundDispatcher: CoroutineDispatcher, @@ -157,8 +147,6 @@ constructor( val surfacePackage: SurfaceControlViewHost.SurfacePackage get() = checkNotNull(host.surfacePackage) - private lateinit var largeClockHostView: FrameLayout - private lateinit var smallClockHostView: FrameLayout private var smartSpaceView: View? = null private val disposables = DisposableHandles() @@ -166,29 +154,18 @@ constructor( private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>() - private val coroutineScope: CoroutineScope - @Style.Type private var themeStyle: Int? = null init { - coroutineScope = - CoroutineScope( - applicationScope.coroutineContext + - Job() + - newTracingContext("KeyguardPreviewRenderer") - ) - disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) - quickAffordancesCombinedViewModel.enablePreviewMode( initiallySelectedSlotId = - bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID) - ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID) + ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, ) - if (MigrateClocksToBlueprint.isEnabled) { - clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance - } + + clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance runBlocking(mainDispatcher) { host = SurfaceControlViewHost( @@ -348,6 +325,7 @@ constructor( smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f } + @OptIn(ExperimentalCoroutinesApi::class) private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) { val keyguardRootView = KeyguardRootView(previewContext, null) rootView.addView( @@ -358,34 +336,23 @@ constructor( ), ) - setUpUdfps( - previewContext, - if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView, - ) + setUpUdfps(previewContext, keyguardRootView) setupShortcuts(keyguardRootView) if (!shouldHideClock) { setUpClock(previewContext, rootView) - if (MigrateClocksToBlueprint.isEnabled) { - KeyguardPreviewClockViewBinder.bind( - keyguardRootView, - clockViewModel, - clockRegistry, - ::updateClockAppearance, - ClockPreviewConfig( - previewContext, - getPreviewShadeLayoutWide(display!!), - SceneContainerFlag.isEnabled, - ), - ) - } else { - KeyguardPreviewClockViewBinder.bind( - largeClockHostView, - smallClockHostView, - clockViewModel, - ) - } + KeyguardPreviewClockViewBinder.bind( + keyguardRootView, + clockViewModel, + clockRegistry, + ::updateClockAppearance, + ClockPreviewConfig( + previewContext, + getPreviewShadeLayoutWide(display!!), + SceneContainerFlag.isEnabled, + ), + ) } setUpSmartspace(previewContext, rootView) @@ -451,82 +418,22 @@ constructor( .inflate(R.layout.udfps_keyguard_preview, parentView, false) as View // Place the UDFPS view in the proper sensor location - if (MigrateClocksToBlueprint.isEnabled) { - val lockId = KeyguardPreviewClockViewBinder.lockId - finger.id = lockId - parentView.addView(finger) - val cs = ConstraintSet() - cs.clone(parentView as ConstraintLayout) - cs.apply { - constrainWidth(lockId, sensorBounds.width()) - constrainHeight(lockId, sensorBounds.height()) - connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top) - connect(lockId, START, PARENT_ID, START, sensorBounds.left) - } - cs.applyTo(parentView) - } else { - val fingerprintLayoutParams = - FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height()) - fingerprintLayoutParams.setMarginsRelative( - sensorBounds.left, - sensorBounds.top, - sensorBounds.right, - sensorBounds.bottom, - ) - parentView.addView(finger, fingerprintLayoutParams) + val lockId = KeyguardPreviewClockViewBinder.lockId + finger.id = lockId + parentView.addView(finger) + val cs = ConstraintSet() + cs.clone(parentView as ConstraintLayout) + cs.apply { + constrainWidth(lockId, sensorBounds.width()) + constrainHeight(lockId, sensorBounds.height()) + connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top) + connect(lockId, START, PARENT_ID, START, sensorBounds.left) } + cs.applyTo(parentView) } private fun setUpClock(previewContext: Context, parentView: ViewGroup) { val resources = parentView.resources - if (!MigrateClocksToBlueprint.isEnabled) { - largeClockHostView = FrameLayout(previewContext) - largeClockHostView.layoutParams = - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT, - ) - largeClockHostView.isInvisible = true - parentView.addView(largeClockHostView) - - smallClockHostView = FrameLayout(previewContext) - val layoutParams = - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - resources.getDimensionPixelSize(customR.dimen.small_clock_height), - ) - layoutParams.topMargin = - SystemBarUtils.getStatusBarHeight(previewContext) + - resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) - smallClockHostView.layoutParams = layoutParams - smallClockHostView.setPaddingRelative( - /* start = */ resources.getDimensionPixelSize(customR.dimen.clock_padding_start), - /* top = */ 0, - /* end = */ 0, - /* bottom = */ 0, - ) - smallClockHostView.clipChildren = false - parentView.addView(smallClockHostView) - smallClockHostView.isInvisible = true - } - - // TODO (b/283465254): Move the listeners to KeyguardClockRepository - if (!MigrateClocksToBlueprint.isEnabled) { - val clockChangeListener = - object : ClockRegistry.ClockChangeListener { - override fun onCurrentClockChanged() { - onClockChanged() - } - } - clockRegistry.registerClockChangeListener(clockChangeListener) - disposables += DisposableHandle { - clockRegistry.unregisterClockChangeListener(clockChangeListener) - } - - clockController.registerListeners(parentView) - disposables += DisposableHandle { clockController.unregisterListeners() } - } - val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -544,38 +451,9 @@ constructor( }, ) disposables += DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) } - - if (!MigrateClocksToBlueprint.isEnabled) { - val layoutChangeListener = - View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - if (clockController.clock !is DefaultClockController) { - clockController.clock - ?.largeClock - ?.events - ?.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(parentView) - ) - clockController.clock - ?.smallClock - ?.events - ?.onTargetRegionChanged( - KeyguardClockSwitch.getSmallClockRegion(parentView) - ) - } - } - parentView.addOnLayoutChangeListener(layoutChangeListener) - disposables += DisposableHandle { - parentView.removeOnLayoutChangeListener(layoutChangeListener) - } - } - - onClockChanged() } private suspend fun updateClockAppearance(clock: ClockController, resources: Resources) { - if (!MigrateClocksToBlueprint.isEnabled) { - clockController.clock = clock - } val colors = wallpaperColors if (clockRegistry.seedColor == null && colors != null) { // Seed color null means users do not override any color on the clock. The default @@ -601,9 +479,7 @@ constructor( // In clock preview, we should have a seed color for clock // before setting clock to clockEventController to avoid updateColor with seedColor == null // So in update colors, it should already have the correct theme in clockFaceController - if (MigrateClocksToBlueprint.isEnabled) { - clockController.clock = clock - } + clockController.clock = clock // When set clock to clockController,it will reset fontsize based on context.resources // We need to override it with overlaid resources clock.largeClock.events.onFontSettingChanged( @@ -611,19 +487,6 @@ constructor( ) } - private fun onClockChanged() { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - coroutineScope.launch { - val clock = clockRegistry.createCurrentClock() - clockController.clock = clock - updateClockAppearance(clock, context.resources) - updateLargeClock(clock) - updateSmallClock(clock) - } - } - private fun setupCommunalTutorialIndicator(keyguardRootView: ConstraintLayout) { keyguardRootView.findViewById<TextView>(R.id.communal_tutorial_indicator)?.let { indicatorView -> @@ -657,34 +520,6 @@ constructor( } } - private fun updateLargeClock(clock: ClockController) { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - clock.largeClock.events.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(largeClockHostView) - ) - if (shouldHighlightSelectedAffordance) { - clock.largeClock.view.alpha = DIM_ALPHA - } - largeClockHostView.removeAllViews() - largeClockHostView.addView(clock.largeClock.view) - } - - private fun updateSmallClock(clock: ClockController) { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - clock.smallClock.events.onTargetRegionChanged( - KeyguardClockSwitch.getSmallClockRegion(smallClockHostView) - ) - if (shouldHighlightSelectedAffordance) { - clock.smallClock.view.alpha = DIM_ALPHA - } - smallClockHostView.removeAllViews() - smallClockHostView.addView(clock.smallClock.view) - } - private fun getPreviewShadeLayoutWide(display: Display): Boolean { return if (display.displayId == 0) { shadeInteractor.isShadeLayoutWide.value diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index 729759a9ad00..5d463f72d8b2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.shade.ShadeDisplayAware @@ -50,16 +49,13 @@ constructor( sharedNotificationContainerBinder, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } constraintSet.apply { connect( R.id.nssl_placeholder, TOP, PARENT_ID, TOP, - context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) + context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin), ) connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 1c897237fe89..fb311a533aa2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -24,7 +24,6 @@ import com.android.app.animation.Interpolators import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -194,12 +193,7 @@ constructor( (!useAltAod) && keyguardClockViewModel.clockSize.value == ClockSize.LARGE val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt() - val translationY = - if (MigrateClocksToBlueprint.isEnabled) { - max(params.topInset - params.minViewY, burnInY) - } else { - max(params.topInset, params.minViewY + burnInY) - params.minViewY - } + val translationY = max(params.topInset - params.minViewY, burnInY) BurnInModel( translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), translationY = translationY, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt new file mode 100644 index 000000000000..890628c31c55 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +class KeyguardQuickAffordanceHapticViewModel +@AssistedInject +constructor( + @Assisted quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel>, + private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, +) { + + private val activatedHistory = MutableStateFlow(ActivatedHistory(false)) + + private val launchingHapticState: Flow<HapticState> = + combine( + quickAffordanceViewModel.map { it.configKey }, + quickAffordanceInteractor.launchingFromTriggeredResult, + ) { key, launchingResult -> + val validKey = key != null && key == launchingResult?.configKey + if (validKey && launchingResult?.launched == true) { + HapticState.LAUNCH + } else { + HapticState.NO_HAPTICS + } + } + .distinctUntilChanged() + + private val toggleHapticState: Flow<HapticState> = + activatedHistory + .map { history -> + when { + history.previousValue == false && history.currentValue -> HapticState.TOGGLE_ON + history.previousValue == true && !history.currentValue -> HapticState.TOGGLE_OFF + else -> HapticState.NO_HAPTICS + } + } + .distinctUntilChanged() + + val quickAffordanceHapticState = + merge(launchingHapticState, toggleHapticState).distinctUntilChanged() + + fun resetLaunchingFromTriggeredResult() = + quickAffordanceInteractor.setLaunchingFromTriggeredResult(null) + + fun updateActivatedHistory(isActivated: Boolean) { + activatedHistory.value = + ActivatedHistory( + currentValue = isActivated, + previousValue = activatedHistory.value.currentValue, + ) + } + + enum class HapticState { + TOGGLE_ON, + TOGGLE_OFF, + LAUNCH, + NO_HAPTICS, + } + + private data class ActivatedHistory( + val currentValue: Boolean, + val previousValue: Boolean? = null, + ) + + @AssistedFactory + interface Factory { + fun create( + quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel> + ): KeyguardQuickAffordanceHapticViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 9066d466ceca..eaba5d5a149c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -29,7 +29,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor import com.android.systemui.keyguard.shared.model.Edge -import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -130,7 +129,6 @@ constructor( PrimaryBouncerToLockscreenTransitionViewModel, private val screenOffAnimationController: ScreenOffAnimationController, private val aodBurnInViewModel: AodBurnInViewModel, - private val aodAlphaViewModel: AodAlphaViewModel, private val shadeInteractor: ShadeInteractor, ) { val burnInLayerVisibility: Flow<Int> = @@ -284,15 +282,6 @@ constructor( .distinctUntilChanged() } - /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */ - @Deprecated("only used for legacy status view") - fun lockscreenStateAlpha(viewState: ViewStateAccessor): Flow<Float> { - return aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState) - } - - /** For elements that appear and move during the animation -> AOD */ - val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha - val translationY: Flow<Float> = aodBurnInViewModel.movement.map { it.translationY.toFloat() } val translationX: Flow<StateToValue> = diff --git a/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt new file mode 100644 index 000000000000..dd2525f5ca45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.lottie + +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieListener +import com.airbnb.lottie.LottieTask +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.suspendCancellableCoroutine + +/** + * Suspends until [LottieTask] is finished with a result or a failure. + * + * @return result of the [LottieTask] when it's successful + */ +suspend fun LottieTask<LottieComposition>.await() = + suspendCancellableCoroutine<LottieComposition> { continuation -> + val resultListener = + LottieListener<LottieComposition> { result -> + with(continuation) { if (!isCancelled && !isCompleted) resume(result) } + } + val failureListener = + LottieListener<Throwable> { throwable -> + with(continuation) { + if (!isCancelled && !isCompleted) resumeWithException(throwable) + } + } + addListener(resultListener) + addFailureListener(failureListener) + continuation.invokeOnCancellation { + removeListener(resultListener) + removeFailureListener(failureListener) + } + } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index c32bd403d2e8..b4dabbe036e9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -34,13 +34,14 @@ import android.view.ViewGroup import android.view.ViewGroupOverlay import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection import com.android.keyguard.KeyguardViewController import com.android.systemui.Flags.mediaControlsLockscreenShadeBugFix import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -68,7 +69,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapLatest -import com.android.app.tracing.coroutines.launchTraced as launch private val TAG: String = MediaHierarchyManager::class.java.simpleName @@ -115,7 +115,7 @@ constructor( wakefulnessLifecycle: WakefulnessLifecycle, shadeInteractor: ShadeInteractor, private val secureSettings: SecureSettings, - @Main private val handler: Handler, + @Background private val handler: Handler, @Application private val coroutineScope: CoroutineScope, private val splitShadeStateController: SplitShadeStateController, private val logger: MediaViewLogger, @@ -631,7 +631,7 @@ constructor( } } } - secureSettings.registerContentObserverForUserSync( + secureSettings.registerContentObserverForUserAsync( Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, settingsObserver, UserHandle.USER_ALL, diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 311cbfb7e632..b2696aeaabfc 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -132,7 +132,7 @@ constructor( val isDefaultNotesAppSet = noteTaskInfoResolver.resolveInfo( QUICK_AFFORDANCE, - user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE), ) != null return when { isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default() @@ -158,7 +158,7 @@ constructor( override fun onTriggered(expandable: Expandable?): OnTriggeredResult { controller.showNoteTask(entryPoint = QUICK_AFFORDANCE) - return OnTriggeredResult.Handled + return OnTriggeredResult.Handled(true) } } @@ -194,7 +194,7 @@ private fun RoleManager.createNotesRoleFlow( fun isDefaultNotesAppSetForUser() = noteTaskInfoResolver.resolveInfo( QUICK_AFFORDANCE, - user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE), ) != null trySendBlocking(isDefaultNotesAppSetForUser()) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt b/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt index ef7e7eb59898..84b995e1cd28 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt @@ -22,16 +22,21 @@ import com.android.systemui.qs.tileimpl.QSTileImpl /** * Creates a [QSTile.Icon] from an [Icon]. - * * [Icon.Loaded] -> [QSTileImpl.DrawableIcon] + * * [Icon.Loaded] with null [res] -> [QSTileImpl.DrawableIcon] + * * [Icon.Loaded] & with non null [res] -> [QSTileImpl.DrawableIconWithRes] * * [Icon.Resource] -> [QSTileImpl.ResourceIcon] */ fun Icon.asQSTileIcon(): QSTile.Icon { return when (this) { is Icon.Loaded -> { - QSTileImpl.DrawableIcon(this.drawable) + if (res == null) { + QSTileImpl.DrawableIcon(drawable) + } else { + QSTileImpl.DrawableIconWithRes(drawable, res) + } } is Icon.Resource -> { - QSTileImpl.ResourceIcon.get(this.res) + QSTileImpl.ResourceIcon.get(res) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt index 790793eab258..3049a40f18c4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt @@ -17,16 +17,16 @@ package com.android.systemui.qs.composefragment.ui import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.drawscope.clipRect -import androidx.compose.ui.graphics.layer.CompositingStrategy -import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.graphicsLayer /** * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out @@ -34,16 +34,16 @@ import androidx.compose.ui.graphics.layer.drawLayer * from the QS container. */ fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier { - return this.drawWithCache { + return this.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .drawWithContent { + drawContent() val params = clipParams() val left = -params.leftInset.toFloat() val right = size.width + params.rightInset.toFloat() val top = params.top.toFloat() val bottom = params.bottom.toFloat() - val graphicsLayer = obtainGraphicsLayer() - graphicsLayer.compositingStrategy = CompositingStrategy.Offscreen - graphicsLayer.record { - drawContent() + val clipSize = Size(right - left, bottom - top) + if (!clipSize.isEmpty()) { clipRect { drawRoundRect( color = Color.Black, @@ -54,9 +54,6 @@ fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams ) } } - onDrawWithContent { - drawLayer(graphicsLayer) - } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 873059ee08db..e7fa27159e9e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -675,17 +675,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } private void add() { - if (addFromPosition(getLayoutPosition())) { - itemView.announceForAccessibility( - itemView.getContext().getText(R.string.accessibility_qs_edit_tile_added)); - } + addFromPosition(getLayoutPosition()); } private void remove() { - if (removeFromPosition(getLayoutPosition())) { - itemView.announceForAccessibility( - itemView.getContext().getText(R.string.accessibility_qs_edit_tile_removed)); - } + removeFromPosition(getLayoutPosition()); } boolean isCurrentTile() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt index 989fc0fd6f44..5ba1527dbf69 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt @@ -22,6 +22,7 @@ import android.os.Looper import android.service.quicksettings.Tile import com.android.internal.logging.MetricsLogger import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -92,7 +93,8 @@ constructor( state?.apply { this.state = tileState.activationState.legacyState - icon = maybeLoadResourceIcon(tileState.iconRes ?: R.drawable.ic_qs_notes) + icon = + maybeLoadResourceIcon((tileState.icon as Icon.Loaded).res ?: R.drawable.ic_qs_notes) label = tileState.label contentDescription = tileState.contentDescription expandedAccessibilityClassName = tileState.expandedAccessibilityClassName diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index 19b45d50c594..7516ca030d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -193,7 +193,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern if (mJob == null) { mJob = WifiUtils.checkWepAllowed(mContext, mCoroutineScope, wifiEntry.getSsid(), WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, intent -> { - mInternetDialogController.startActivity(intent, view); + mInternetDialogController.startActivityForDialog(intent); return null; }, () -> { wifiConnect(wifiEntry, view); @@ -211,7 +211,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern true /* connectForCaller */); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - mContext.startActivity(intent); + mInternetDialogController.startActivityForDialog(intent); return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index dbe1ae90b3f6..7036ef914a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -781,6 +781,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller); } + void startActivityForDialog(Intent intent) { + mActivityStarter.startActivity(intent, false /* dismissShade */); + } + void launchNetworkSetting(View view) { startActivity(getSettingsIntent(), view); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt index 34c2ec90f1e8..80d429ce2716 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt @@ -35,13 +35,13 @@ constructor(@ShadeDisplayAware private val resources: Resources, val theme: Them override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - iconRes = + val iconRes = if (data.isEnabled) { R.drawable.qs_airplane_icon_on } else { R.drawable.qs_airplane_icon_off } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt index a72992db4496..d56d9944dbb8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt @@ -84,8 +84,8 @@ constructor( secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm) } } - iconRes = R.drawable.ic_alarm - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + val iconRes = R.drawable.ic_alarm + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) sideViewIcon = QSTileState.SideViewIcon.Chevron contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt index e116d8cef2ee..72759c5bb066 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt @@ -38,10 +38,10 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.battery_detail_switch_title) contentDescription = label - iconRes = + val iconRes = if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on else R.drawable.qs_battery_saver_icon_off - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) sideViewIcon = QSTileState.SideViewIcon.None if (data.isPluggedIn) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt index 21b9f659dde4..e5a0fe8ed048 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt @@ -37,8 +37,8 @@ constructor( override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction) - iconRes = R.drawable.ic_qs_color_correction - icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null) + val iconRes = R.drawable.ic_qs_color_correction + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt index 2dfb1fc4fe98..32ccba6f1fa5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt @@ -35,14 +35,14 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - iconRes = + val iconRes = if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) { R.drawable.qs_flashlight_icon_on } else { R.drawable.qs_flashlight_icon_off } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) contentDescription = label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt index 7f41cbd322dd..c571b136e18b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt @@ -36,8 +36,8 @@ constructor( override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - iconRes = R.drawable.ic_qs_font_scaling - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + val iconRes = R.drawable.ic_qs_font_scaling + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) contentDescription = label activationState = QSTileState.ActivationState.ACTIVE sideViewIcon = QSTileState.SideViewIcon.Chevron diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt index 4c302b363c3b..12f71491c7b4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt @@ -37,8 +37,8 @@ constructor( override fun map(config: QSTileConfig, data: HearingDevicesTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.quick_settings_hearing_devices_label) - iconRes = R.drawable.qs_hearing_devices_icon - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + val iconRes = R.drawable.qs_hearing_devices_icon + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) sideViewIcon = QSTileState.SideViewIcon.Chevron contentDescription = label if (data.isAnyActiveHearingDevice) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt index 1a6876d0b765..7ad01e463399 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt @@ -61,11 +61,11 @@ constructor( when (val dataIcon = data.icon) { is InternetTileIconModel.ResourceId -> { - iconRes = dataIcon.resId icon = Icon.Loaded( resources.getDrawable(dataIcon.resId, theme), contentDescription = null, + dataIcon.resId, ) } @@ -76,11 +76,11 @@ constructor( } is InternetTileIconModel.Satellite -> { - iconRes = dataIcon.resourceIcon.res // level is inferred from res icon = Icon.Loaded( resources.getDrawable(dataIcon.resourceIcon.res, theme), contentDescription = null, + dataIcon.resourceIcon.res, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt index 8d35b2413bad..05590e803ffa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt @@ -35,7 +35,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_inversion) - + val iconRes: Int if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] @@ -45,7 +45,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the secondaryLabel = subtitleArray[1] iconRes = R.drawable.qs_invert_colors_icon_off } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt index 3557c1a4ac9d..afb137e1e92f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt @@ -39,6 +39,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the Icon.Loaded( resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme), null, + R.drawable.qs_record_issue_icon_on, ) } else { activationState = QSTileState.ActivationState.INACTIVE @@ -46,6 +47,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the Icon.Loaded( resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme), null, + R.drawable.qs_record_issue_icon_off, ) } supportedActions = setOf(QSTileState.UserAction.CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt index dfc24a10c491..ced5a4f099a2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -35,13 +35,13 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - iconRes = + val iconRes = if (data.isEnabled) { R.drawable.qs_location_icon_on } else { R.drawable.qs_location_icon_off } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) label = resources.getString(R.string.quick_settings_location_label) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index 9b2880b6d47f..bb08022ee084 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -73,14 +73,15 @@ constructor( return ModesTileModel( isActivated = activeModes.isAnyActive(), icon = tileIcon.icon, - iconResId = tileIcon.resId, activeModes = activeModes.modeNames, ) } else { return ModesTileModel( isActivated = activeModes.isAnyActive(), - icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), - iconResId = ModesTile.ICON_RES_ID, + icon = + context + .getDrawable(ModesTile.ICON_RES_ID)!! + .asIcon(res = ModesTile.ICON_RES_ID), activeModes = activeModes.modeNames, ) } @@ -92,12 +93,18 @@ constructor( return if (activeMode != null) { // ZenIconKey.resPackage is null if its resId is a system icon. if (activeMode.icon.key.resPackage == null) { - TileIcon(activeMode.icon.drawable.asIcon(), activeMode.icon.key.resId) + TileIcon( + activeMode.icon.drawable.asIcon(res = activeMode.icon.key.resId), + activeMode.icon.key.resId, + ) } else { TileIcon(activeMode.icon.drawable.asIcon(), null) } } else { - TileIcon(context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), ModesTile.ICON_RES_ID) + TileIcon( + context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(res = ModesTile.ICON_RES_ID), + ModesTile.ICON_RES_ID, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt index db4812342050..d0eacbc9a957 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt @@ -21,12 +21,10 @@ import com.android.systemui.common.shared.model.Icon data class ModesTileModel( val isActivated: Boolean, val activeModes: List<String>, - val icon: Icon.Loaded, - /** - * Resource id corresponding to [icon]. Will only be present if it's know to correspond to a - * resource with a known id in SystemUI (such as resources from `android.R`, - * `com.android.internal.R`, or `com.android.systemui.res` itself). + * icon.res will only be present if it is known to correspond to a resource with a known id in + * SystemUI (such as resources from `android.R`, `com.android.internal.R`, or + * `com.android.systemui.res` itself). */ - val iconResId: Int? = null + val icon: Icon.Loaded, ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt index 1507ef4b3b58..99ae3b8db709 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt @@ -34,7 +34,6 @@ constructor(@ShadeDisplayAware private val resources: Resources, val theme: Reso QSTileDataToStateMapper<ModesTileModel> { override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - iconRes = data.iconResId icon = data.icon activationState = if (data.isActivated) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt index 3569e4d0b42c..16b36289ad95 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt @@ -49,7 +49,7 @@ constructor( supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) sideViewIcon = QSTileState.SideViewIcon.None - + val iconRes: Int if (data.isActivated) { activationState = QSTileState.ActivationState.ACTIVE iconRes = R.drawable.qs_nightlight_icon_on @@ -58,7 +58,7 @@ constructor( iconRes = R.drawable.qs_nightlight_icon_off } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) secondaryLabel = getSecondaryLabel(data, resources) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt index a5436192af39..ecdd71170cda 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt @@ -35,8 +35,8 @@ constructor( ) : QSTileDataToStateMapper<NotesTileModel> { override fun map(config: QSTileConfig, data: NotesTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - iconRes = R.drawable.ic_qs_notes - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) + val iconRes = R.drawable.ic_qs_notes + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) contentDescription = label activationState = QSTileState.ActivationState.INACTIVE sideViewIcon = QSTileState.SideViewIcon.Chevron diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt index 76f1e8b8760c..5b3ea93ab1ae 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt @@ -38,8 +38,8 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded) label = resources.getString(R.string.quick_settings_onehanded_label) - iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + val iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt index c546250e73d2..21e92d3a1972 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt @@ -38,8 +38,8 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.qr_code_scanner_title) contentDescription = label - iconRes = R.drawable.ic_qr_code_scanner - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + val iconRes = R.drawable.ic_qr_code_scanner + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) sideViewIcon = QSTileState.SideViewIcon.Chevron supportedActions = setOf(QSTileState.UserAction.CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt index 66d0f96fdcde..66759cdfd1a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt @@ -37,6 +37,7 @@ constructor( override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { + val iconRes: Int if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE iconRes = R.drawable.qs_extra_dim_icon_on @@ -50,7 +51,7 @@ constructor( resources .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE] } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) label = resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name) contentDescription = label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt index a0144221577d..000c7025e32b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt @@ -42,7 +42,7 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.quick_settings_rotation_unlocked_label) contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation) - + val iconRes: Int if (data.isRotationLocked) { activationState = QSTileState.ActivationState.INACTIVE secondaryLabel = EMPTY_SECONDARY_STRING @@ -57,7 +57,7 @@ constructor( } iconRes = R.drawable.qs_auto_rotate_icon_on } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) if (isDeviceFoldable(resources, deviceStateManager)) { secondaryLabel = getSecondaryLabelWithPosture(activationState) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt index aea4967c546c..1d5cf29f2462 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt @@ -36,6 +36,7 @@ constructor( override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { with(data) { + val iconRes: Int if (isEnabled) { activationState = QSTileState.ActivationState.ACTIVE iconRes = R.drawable.qs_data_saver_icon_on @@ -45,7 +46,7 @@ constructor( iconRes = R.drawable.qs_data_saver_icon_off secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1] } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt index f3136e015acf..0a61e3cbe616 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt @@ -38,7 +38,7 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.quick_settings_screen_record_label) supportedActions = setOf(QSTileState.UserAction.CLICK) - + val iconRes: Int when (data) { is ScreenRecordModel.Recording -> { activationState = QSTileState.ActivationState.ACTIVE @@ -61,7 +61,7 @@ constructor( resources.getString(R.string.quick_settings_screen_record_start) } } - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) contentDescription = if (TextUtils.isEmpty(secondaryLabel)) label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt index 73e61b7d178e..f54f46c01dee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt @@ -50,8 +50,8 @@ constructor( contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) - iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked) - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + val iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) sideViewIcon = QSTileState.SideViewIcon.None if (data.isBlocked) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt index e9aa46c5f253..5933d65bc61f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt @@ -116,11 +116,11 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the } } - iconRes = + val iconRes = if (activationState == QSTileState.ActivationState.ACTIVE) R.drawable.qs_light_dark_theme_icon_on else R.drawable.qs_light_dark_theme_icon_off - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) supportedActions = if (activationState == QSTileState.ActivationState.UNAVAILABLE) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt index 6a3195a493c8..5b462ba074ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt @@ -41,8 +41,8 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = getTileLabel()!! contentDescription = label - iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status - icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) + val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status + icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes) when (data) { is WorkModeTileModel.HasActiveProfile -> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index 8394be5e0a38..c6af729cd4a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -36,7 +36,6 @@ import kotlin.reflect.KClass */ data class QSTileState( val icon: Icon?, - val iconRes: Int?, val label: CharSequence, val activationState: ActivationState, val secondaryLabel: CharSequence?, @@ -58,7 +57,7 @@ data class QSTileState( ): QSTileState { val iconDrawable = resources.getDrawable(config.iconRes, theme) return build( - Icon.Loaded(iconDrawable, null), + Icon.Loaded(iconDrawable, null, config.iconRes), resources.getString(config.labelRes), builder, ) @@ -115,7 +114,6 @@ data class QSTileState( } class Builder(var icon: Icon?, var label: CharSequence) { - var iconRes: Int? = null var activationState: ActivationState = ActivationState.INACTIVE var secondaryLabel: CharSequence? = null var supportedActions: Set<UserAction> = setOf(UserAction.CLICK) @@ -128,7 +126,6 @@ data class QSTileState( fun build(): QSTileState = QSTileState( icon, - iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 632eeefcb462..c34edc81bfe7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -260,8 +260,8 @@ constructor( icon = when (val stateIcon = viewModelState.icon) { is Icon.Loaded -> - if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable) - else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes) + if (stateIcon.res == null) DrawableIcon(stateIcon.drawable) + else DrawableIconWithRes(stateIcon.drawable, stateIcon.res) is Icon.Resource -> ResourceIcon.get(stateIcon.res) null -> null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index 66af275bc702..a7dbb47bc609 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel import android.view.View +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor @@ -99,6 +100,17 @@ constructor( ) } + if (Flags.promoteNotificationsAutomatically()) { + // When we're promoting notifications automatically, the `when` time set on the + // notification will likely just be set to the current time, which would cause the chip + // to always show "now". We don't want early testers to get that experience since it's + // not what will happen at launch, so just don't show any time. + // TODO(b/364653005): Only ignore the `when` time if the notification was + // *automatically* promoted (as opposed to being legitimately promoted by the + // criteria). We'll need to track that status somehow. + return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + } + if (this.promotedContent.time == null) { return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt new file mode 100644 index 000000000000..9f523fc845ab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.chips.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the status bar popup chips flag state. */ +@Suppress("NOTHING_TO_INLINE") +object StatusBarPopupChips { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_POPUP_CHIPS + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.statusBarPopupChips() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + * Caution!! Using this check incorrectly will cause crashes in nextfood builds! + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt new file mode 100644 index 000000000000..1663aebd7287 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.shared.model + +import com.android.systemui.common.shared.model.Icon + +/** + * Ids used to track different types of popup chips. Will be used to ensure only one chip is + * displaying its popup at a time. + */ +sealed class PopupChipId(val value: String) { + data object MediaControls : PopupChipId("MediaControls") +} + +/** Model for individual status bar popup chips. */ +sealed class PopupChipModel { + abstract val logName: String + abstract val chipId: PopupChipId + + data class Hidden(override val chipId: PopupChipId, val shouldAnimate: Boolean = true) : + PopupChipModel() { + override val logName = "Hidden(id=$chipId, anim=$shouldAnimate)" + } + + data class Shown( + override val chipId: PopupChipId, + val icon: Icon, + val chipText: String, + val isToggled: Boolean = false, + val onToggle: () -> Unit, + val onIconPressed: () -> Unit, + ) : PopupChipModel() { + override val logName = "Shown(id=$chipId, toggled=$isToggled)" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt new file mode 100644 index 000000000000..5712be30ccd6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel + +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel +import kotlinx.coroutines.flow.StateFlow + +/** + * Interface for a view model that knows the display requirements for a single type of status bar + * popup chip. + */ +interface StatusBarPopupChipViewModel { + /** A flow modeling the popup chip that should be shown (or not shown). */ + val chip: StateFlow<PopupChipModel> +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt new file mode 100644 index 000000000000..b390f29b166c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * View model deciding which system process chips to show in the status bar. Emits a list of + * PopupChipModels. + */ +@SysUISingleton +class StatusBarPopupChipsViewModel @Inject constructor(@Background scope: CoroutineScope) { + private data class PopupChipBundle( + val media: PopupChipModel = PopupChipModel.Hidden(chipId = PopupChipId.MediaControls) + ) + + private val incomingPopupChipBundle: Flow<PopupChipBundle?> = + flowOf(null).stateIn(scope, SharingStarted.Lazily, PopupChipBundle()) + + val popupChips: Flow<List<PopupChipModel>> = + incomingPopupChipBundle + .map { _ -> listOf(null).filterIsInstance<PopupChipModel.Shown>() } + .stateIn(scope, SharingStarted.Lazily, emptyList()) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 80e8f55b897a..d83acf34ca99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -186,6 +187,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); if (AsyncHybridViewInflation.isEnabled()) { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE); + if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); + } } mRowContentBindStage.requestRebind(entry, null); } @@ -256,10 +260,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED); params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); params.setUseMinimized(isMinimized); - // TODO b/358403414: use the different types of redaction - boolean needsRedaction = inflaterParams.getRedactionType() != REDACTION_TYPE_NONE; + int redactionType = inflaterParams.getRedactionType(); - if (needsRedaction) { + params.setRedactionType(redactionType); + if (redactionType != REDACTION_TYPE_NONE) { params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); } else { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); @@ -276,8 +280,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { - - if (inflaterParams.isChildInGroup() && needsRedaction) { + if (inflaterParams.isChildInGroup() + && redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); } else { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt index 13ad1413e89d..a43f8dbc1b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.promoted import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.ERROR import com.android.systemui.log.core.LogLevel.INFO -import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel @@ -27,7 +26,7 @@ import javax.inject.Inject class PromotedNotificationLogger @Inject -constructor(@NotificationLog private val buffer: LogBuffer) { +constructor(@PromotedNotificationLog private val buffer: LogBuffer) { fun logExtractionSkipped(entry: NotificationEntry, reason: String) { buffer.log( EXTRACTION_TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt new file mode 100644 index 000000000000..0f21514fcc94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +@SysUISingleton +class AODPromotedNotificationInteractor +@Inject +constructor(activeNotificationsInteractor: ActiveNotificationsInteractor) { + val content: Flow<PromotedNotificationContentModel?> = + activeNotificationsInteractor.topLevelRepresentativeNotifications.map { notifs -> + notifs.firstNotNullOfOrNull { it.promotedContent } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index fe2dabe1ba8a..74809fd8622f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -28,7 +28,7 @@ import com.android.systemui.statusbar.notification.promoted.PromotedNotification * like the skeleton view on AOD or the status bar chip. */ data class PromotedNotificationContentModel( - val key: String, + val identity: Identity, // for all styles: val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel. @@ -82,7 +82,7 @@ data class PromotedNotificationContentModel( fun build() = PromotedNotificationContentModel( - key = key, + identity = Identity(key, style), skeletonSmallIcon = skeletonSmallIcon, appName = appName, subText = subText, @@ -103,6 +103,8 @@ data class PromotedNotificationContentModel( ) } + data class Identity(val key: String, val style: Style) + /** The timestamp associated with a notification, along with the mode used to display it. */ data class When(val time: Long, val mode: Mode) { /** The mode used to display a notification's `when` value. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt new file mode 100644 index 000000000000..adfa6a10814d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Identity +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +@SysUISingleton +class AODPromotedNotificationViewModel +@Inject +constructor(interactor: AODPromotedNotificationInteractor) { + private val content: Flow<PromotedNotificationContentModel?> = interactor.content + private val identity: Flow<Identity?> = content.mapNonNullsKeepingNulls { it.identity } + + val notification: Flow<PromotedNotificationViewModel?> = + identity.distinctUntilChanged().mapNonNullsKeepingNulls { identity -> + val updates = interactor.content.filterNotNull().filter { it.identity == identity } + PromotedNotificationViewModel(identity, updates) + } +} + +private fun <T, R> Flow<T?>.mapNonNullsKeepingNulls(block: (T) -> R): Flow<R?> = map { + it?.let(block) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt new file mode 100644 index 000000000000..f265e0ff33f8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted.ui.viewmodel + +import android.graphics.drawable.Icon +import com.android.internal.widget.NotificationProgressModel +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class PromotedNotificationViewModel( + identity: PromotedNotificationContentModel.Identity, + content: Flow<PromotedNotificationContentModel>, +) { + // for all styles: + + val key: String = identity.key + val style: Style = identity.style + + val skeletonSmallIcon: Flow<Icon?> = content.map { it.skeletonSmallIcon } + val appName: Flow<CharSequence?> = content.map { it.appName } + val subText: Flow<CharSequence?> = content.map { it.subText } + + private val time: Flow<When?> = content.map { it.time } + val whenTime: Flow<Long?> = time.map { it?.time } + val whenMode: Flow<When.Mode?> = time.map { it?.mode } + + val lastAudiblyAlertedMs: Flow<Long> = content.map { it.lastAudiblyAlertedMs } + val profileBadgeResId: Flow<Int?> = content.map { it.profileBadgeResId } + val title: Flow<CharSequence?> = content.map { it.title } + val text: Flow<CharSequence?> = content.map { it.text } + val skeletonLargeIcon: Flow<Icon?> = content.map { it.skeletonLargeIcon } + + // for CallStyle: + val personIcon: Flow<Icon?> = content.map { it.personIcon } + val personName: Flow<CharSequence?> = content.map { it.personName } + val verificationIcon: Flow<Icon?> = content.map { it.verificationIcon } + val verificationText: Flow<CharSequence?> = content.map { it.verificationText } + + // for ProgressStyle: + val progress: Flow<NotificationProgressModel?> = content.map { it.progress } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 6eeb80d45211..70e27a981b49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; @@ -25,6 +26,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationConten import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.Notification.MessagingStyle; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; @@ -161,9 +163,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry, mConversationProcessor, row, - bindParams.isMinimized, - bindParams.usesIncreasedHeight, - bindParams.usesIncreasedHeadsUpHeight, + bindParams, callback, mRemoteInputManager.getRemoteViewsOnClickHandler(), /* isMediaFlagEnabled = */ mIsMediaInQS, @@ -187,13 +187,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder boolean inflateSynchronously, @InflationFlag int reInflateFlags, Notification.Builder builder, + Context systemUiContext, Context packageContext, SmartReplyStateInflater smartRepliesInflater) { InflationProgress result = createRemoteViews(reInflateFlags, builder, - bindParams.isMinimized, - bindParams.usesIncreasedHeight, - bindParams.usesIncreasedHeadsUpHeight, + bindParams, + systemUiContext, packageContext, row, mNotifLayoutInflaterFactoryProvider, @@ -203,18 +203,20 @@ public class NotificationContentInflater implements NotificationRowContentBinder result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger); boolean isConversation = entry.getRanking().isConversation(); + Notification.MessagingStyle messagingStyle = null; + if (isConversation && (AsyncHybridViewInflation.isEnabled() + || LockscreenOtpRedaction.isSingleLineViewEnabled())) { + messagingStyle = mConversationProcessor + .processNotification(entry, builder, mLogger); + } if (AsyncHybridViewInflation.isEnabled()) { - Notification.MessagingStyle messagingStyle = null; - if (isConversation) { - messagingStyle = mConversationProcessor - .processNotification(entry, builder, mLogger); - } SingleLineViewModel viewModel = SingleLineViewInflater .inflateSingleLineViewModel( entry.getSbn().getNotification(), messagingStyle, builder, - row.getContext() + row.getContext(), + false ); // If the messagingStyle is null, we want to inflate the normal view isConversation = viewModel.isConversation(); @@ -228,11 +230,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder mLogger ); } - if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { - result.mPublicInflatedSingleLineViewModel = - SingleLineViewInflater.inflateRedactedSingleLineViewModel(row.getContext(), - isConversation); + if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateSingleLineViewModel( + entry.getSbn().getNotification(), + messagingStyle, + builder, + row.getContext(), + true); + } else { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateRedactedSingleLineViewModel( + row.getContext(), + isConversation + ); + } result.mPublicInflatedSingleLineView = SingleLineViewInflater.inflatePublicSingleLineView( isConversation, @@ -411,8 +424,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags, - Notification.Builder builder, boolean isMinimized, boolean usesIncreasedHeight, - boolean usesIncreasedHeadsUpHeight, Context packageContext, + Notification.Builder builder, BindParams bindParams, Context systemUiContext, + Context packageContext, ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, @@ -423,13 +436,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view"); - result.newContentView = createContentView(builder, isMinimized, - usesIncreasedHeight); + result.newContentView = createContentView(builder, bindParams.isMinimized, + bindParams.usesIncreasedHeight); } if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view"); - result.newExpandedView = createExpandedView(builder, isMinimized); + result.newExpandedView = createExpandedView(builder, bindParams.isMinimized); } if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { @@ -439,13 +452,20 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.newHeadsUpView = builder.createCompactHeadsUpContentView(); } else { result.newHeadsUpView = builder.createHeadsUpContentView( - usesIncreasedHeadsUpHeight); + bindParams.usesIncreasedHeadsUpHeight); } } if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating public remote view"); - result.newPublicView = builder.makePublicContentView(isMinimized); + if (LockscreenOtpRedaction.isEnabled() + && bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + result.newPublicView = createSensitiveContentMessageNotification( + row.getEntry().getSbn().getNotification(), builder.getStyle(), + systemUiContext, packageContext).createContentView(true); + } else { + result.newPublicView = builder.makePublicContentView(bindParams.isMinimized); + } } if (AsyncGroupHeaderViewInflation.isEnabled()) { @@ -473,6 +493,42 @@ public class NotificationContentInflater implements NotificationRowContentBinder }); } + private static Notification.Builder createSensitiveContentMessageNotification( + Notification original, + Notification.Style originalStyle, + Context systemUiContext, + Context packageContext) { + Notification.Builder redacted = + new Notification.Builder(packageContext, original.getChannelId()); + redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE)); + CharSequence redactedMessage = systemUiContext.getString( + R.string.redacted_notification_single_line_text + ); + + if (originalStyle instanceof MessagingStyle oldStyle) { + MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser()); + newStyle.setConversationTitle(oldStyle.getConversationTitle()); + newStyle.setGroupConversation(false); + newStyle.setConversationType(oldStyle.getConversationType()); + newStyle.setShortcutIcon(oldStyle.getShortcutIcon()); + newStyle.setBuilder(redacted); + MessagingStyle.Message latestMessage = + MessagingStyle.findLatestIncomingMessage(oldStyle.getMessages()); + if (latestMessage != null) { + MessagingStyle.Message newMessage = new MessagingStyle.Message(redactedMessage, + latestMessage.getTimestamp(), latestMessage.getSenderPerson()); + newStyle.addMessage(newMessage); + } + redacted.setStyle(newStyle); + } else { + redacted.setContentText(redactedMessage); + } + redacted.setLargeIcon(original.getLargeIcon()); + redacted.setSmallIcon(original.getSmallIcon()); + return redacted; + } + + private static void setNotifsViewsInflaterFactory(InflationProgress result, ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { @@ -1118,10 +1174,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotificationEntry mEntry; private final Context mContext; private final boolean mInflateSynchronously; - private final boolean mIsMinimized; - private final boolean mUsesIncreasedHeight; + private final BindParams mBindParams; private final InflationCallback mCallback; - private final boolean mUsesIncreasedHeadsUpHeight; private final @InflationFlag int mReInflateFlags; private final NotifRemoteViewCache mRemoteViewCache; private final Executor mInflationExecutor; @@ -1145,9 +1199,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder NotificationEntry entry, ConversationNotificationProcessor conversationProcessor, ExpandableNotificationRow row, - boolean isMinimized, - boolean usesIncreasedHeight, - boolean usesIncreasedHeadsUpHeight, + BindParams bindParams, InflationCallback callback, RemoteViews.InteractionHandler remoteViewClickHandler, boolean isMediaFlagEnabled, @@ -1164,9 +1216,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteViewCache = cache; mSmartRepliesInflater = smartRepliesInflater; mContext = mRow.getContext(); - mIsMinimized = isMinimized; - mUsesIncreasedHeight = usesIncreasedHeight; - mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; + mBindParams = bindParams; mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; mConversationProcessor = conversationProcessor; @@ -1236,8 +1286,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mEntry, recoveredBuilder, mLogger); } InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, - recoveredBuilder, mIsMinimized, mUsesIncreasedHeight, - mUsesIncreasedHeadsUpHeight, packageContext, mRow, + recoveredBuilder, mBindParams, mContext, packageContext, mRow, mNotifLayoutInflaterFactoryProvider, mHeadsUpStyleProvider, mLogger); mLogger.logAsyncTaskProgress(mEntry, @@ -1264,7 +1313,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder mEntry.getSbn().getNotification(), messagingStyle, recoveredBuilder, - mContext + mContext, + false ); result.mInflatedSingleLineView = SingleLineViewInflater.inflatePrivateSingleLineView( @@ -1277,9 +1327,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder } if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { - result.mPublicInflatedSingleLineViewModel = - SingleLineViewInflater.inflateRedactedSingleLineViewModel(mContext, - isConversation); + if (mBindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateSingleLineViewModel( + mEntry.getSbn().getNotification(), + messagingStyle, + recoveredBuilder, + mContext, + true + ); + } else { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateRedactedSingleLineViewModel( + mContext, + isConversation + ); + } result.mPublicInflatedSingleLineView = SingleLineViewInflater.inflatePublicSingleLineView( isConversation, @@ -1320,7 +1383,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mCancellationSignal = apply( mInflationExecutor, mInflateSynchronously, - mIsMinimized, + mBindParams.isMinimized, result, mReInflateFlags, mRemoteViewCache, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index 07384afe2d2e..1cef8791e0ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -141,20 +143,33 @@ public interface NotificationRowContentBinder { */ class BindParams { + public BindParams(boolean minimized, boolean increasedHeight, + boolean increasedHeadsUpHeight, int redaction) { + isMinimized = minimized; + usesIncreasedHeight = increasedHeight; + usesIncreasedHeadsUpHeight = increasedHeadsUpHeight; + redactionType = redaction; + } + /** * Bind a minimized version of the content views. */ - public boolean isMinimized; + public final boolean isMinimized; /** * Use increased height when binding contracted view. */ - public boolean usesIncreasedHeight; + public final boolean usesIncreasedHeight; /** * Use increased height when binding heads up views. */ - public boolean usesIncreasedHeadsUpHeight; + public final boolean usesIncreasedHeadsUpHeight; + + /** + * Controls the type of public view to show, if a public view is requested + */ + public final @RedactionType int redactionType; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index 7dcb2de57e56..c619b17f1ad8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row import android.annotation.SuppressLint import android.app.Notification +import android.app.Notification.MessagingStyle import android.content.Context import android.content.ContextWrapper import android.content.pm.ApplicationInfo @@ -42,6 +43,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.NotifInflation import com.android.systemui.res.R import com.android.systemui.statusbar.InflationTask +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.InflationException @@ -142,9 +144,7 @@ constructor( entry, conversationProcessor, row, - bindParams.isMinimized, - bindParams.usesIncreasedHeight, - bindParams.usesIncreasedHeadsUpHeight, + bindParams, callback, remoteInputManager.remoteViewsOnClickHandler, /* isMediaFlagEnabled = */ smartReplyStateInflater, @@ -178,10 +178,8 @@ constructor( reInflateFlags = reInflateFlags, entry = entry, builder = builder, - isMinimized = bindParams.isMinimized, - usesIncreasedHeight = bindParams.usesIncreasedHeight, - usesIncreasedHeadsUpHeight = bindParams.usesIncreasedHeadsUpHeight, - systemUIContext = systemUIContext, + bindParams, + systemUiContext = systemUIContext, packageContext = packageContext, row = row, notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, @@ -370,9 +368,7 @@ constructor( private val entry: NotificationEntry, private val conversationProcessor: ConversationNotificationProcessor, private val row: ExpandableNotificationRow, - private val isMinimized: Boolean, - private val usesIncreasedHeight: Boolean, - private val usesIncreasedHeadsUpHeight: Boolean, + private val bindParams: BindParams, private val callback: InflationCallback?, private val remoteViewClickHandler: InteractionHandler?, private val smartRepliesInflater: SmartReplyStateInflater, @@ -440,10 +436,8 @@ constructor( reInflateFlags = reInflateFlags, entry = entry, builder = recoveredBuilder, - isMinimized = isMinimized, - usesIncreasedHeight = usesIncreasedHeight, - usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight, - systemUIContext = context, + bindParams = bindParams, + systemUiContext = context, packageContext = packageContext, row = row, notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, @@ -513,7 +507,7 @@ constructor( apply( inflationExecutor, inflateSynchronously, - isMinimized, + bindParams.isMinimized, progress, reInflateFlags, remoteViewCache, @@ -670,10 +664,8 @@ constructor( @InflationFlag reInflateFlags: Int, entry: NotificationEntry, builder: Notification.Builder, - isMinimized: Boolean, - usesIncreasedHeight: Boolean, - usesIncreasedHeadsUpHeight: Boolean, - systemUIContext: Context, + bindParams: BindParams, + systemUiContext: Context, packageContext: Context, row: ExpandableNotificationRow, notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, @@ -705,9 +697,10 @@ constructor( createRemoteViews( reInflateFlags = reInflateFlags, builder = builder, - isMinimized = isMinimized, - usesIncreasedHeight = usesIncreasedHeight, - usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight, + bindParams = bindParams, + entry = entry, + systemUiContext = systemUiContext, + packageContext = packageContext, row = row, notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, headsUpStyleProvider = headsUpStyleProvider, @@ -724,7 +717,8 @@ constructor( notification = entry.sbn.notification, messagingStyle = messagingStyle, builder = builder, - systemUiContext = systemUIContext, + systemUiContext = systemUiContext, + redactText = false, ) } else null @@ -734,10 +728,20 @@ constructor( reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0 ) { logger.logAsyncTaskProgress(entry, "inflating public single line view model") - SingleLineViewInflater.inflateRedactedSingleLineViewModel( - systemUIContext, - entry.ranking.isConversation, - ) + if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + SingleLineViewInflater.inflateSingleLineViewModel( + notification = entry.sbn.notification, + messagingStyle = messagingStyle, + builder = builder, + systemUiContext = systemUiContext, + redactText = true, + ) + } else { + SingleLineViewInflater.inflateRedactedSingleLineViewModel( + systemUiContext, + entry.ranking.isConversation, + ) + } } else null val headsUpStatusBarModel = @@ -761,12 +765,50 @@ constructor( ) } + private fun createSensitiveContentMessageNotification( + original: Notification, + originalStyle: Notification.Style?, + sysUiContext: Context, + packageContext: Context, + ): Notification.Builder { + val redacted = Notification.Builder(packageContext, original.channelId) + redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE)) + val redactedMessage = + sysUiContext.getString(R.string.redacted_notification_single_line_text) + + if (originalStyle is MessagingStyle) { + val newStyle = MessagingStyle(originalStyle.user) + newStyle.conversationTitle = originalStyle.conversationTitle + newStyle.isGroupConversation = false + newStyle.conversationType = originalStyle.conversationType + newStyle.shortcutIcon = originalStyle.shortcutIcon + newStyle.setBuilder(redacted) + val latestMessage = MessagingStyle.findLatestIncomingMessage(originalStyle.messages) + if (latestMessage != null) { + val newMessage = + MessagingStyle.Message( + redactedMessage, + latestMessage.timestamp, + latestMessage.senderPerson, + ) + newStyle.addMessage(newMessage) + } + redacted.style = newStyle + } else { + redacted.setContentText(redactedMessage) + } + redacted.setLargeIcon(original.getLargeIcon()) + redacted.setSmallIcon(original.smallIcon) + return redacted + } + private fun createRemoteViews( @InflationFlag reInflateFlags: Int, builder: Notification.Builder, - isMinimized: Boolean, - usesIncreasedHeight: Boolean, - usesIncreasedHeadsUpHeight: Boolean, + bindParams: BindParams, + entry: NotificationEntry, + systemUiContext: Context, + packageContext: Context, row: ExpandableNotificationRow, notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, headsUpStyleProvider: HeadsUpStyleProvider, @@ -780,7 +822,11 @@ constructor( entryForLogging, "creating contracted remote view", ) - createContentView(builder, isMinimized, usesIncreasedHeight) + createContentView( + builder, + bindParams.isMinimized, + bindParams.usesIncreasedHeight, + ) } else null val expanded = if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { @@ -788,7 +834,7 @@ constructor( entryForLogging, "creating expanded remote view", ) - createExpandedView(builder, isMinimized) + createExpandedView(builder, bindParams.isMinimized) } else null val headsUp = if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) { @@ -800,13 +846,26 @@ constructor( if (isHeadsUpCompact) { builder.createCompactHeadsUpContentView() } else { - builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight) + builder.createHeadsUpContentView(bindParams.usesIncreasedHeadsUpHeight) } } else null val public = if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating public remote view") - builder.makePublicContentView(isMinimized) + if ( + LockscreenOtpRedaction.isEnabled && + bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT + ) { + createSensitiveContentMessageNotification( + entry.sbn.notification, + builder.style, + systemUiContext, + packageContext, + ) + .createContentView(bindParams.usesIncreasedHeight) + } else { + builder.makePublicContentView(bindParams.isMinimized) + } } else null val normalGroupHeader = if ( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 427fb66ca2d0..bc44cb0e1074 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; @@ -31,6 +33,7 @@ public final class RowContentBindParams { private boolean mUseIncreasedHeadsUpHeight; private boolean mViewsNeedReinflation; private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS; + private @RedactionType int mRedactionType = REDACTION_TYPE_NONE; /** * Content views that are out of date and need to be rebound. @@ -58,6 +61,20 @@ public final class RowContentBindParams { } /** + * @return What type of redaction should be used by the public view (if requested) + */ + public @RedactionType int getRedactionType() { + return mRedactionType; + } + + /** + * Set the redaction type, which controls what sort of public view is shown. + */ + public void setRedactionType(@RedactionType int redactionType) { + mRedactionType = redactionType; + } + + /** * Set whether content should use an increased height version of its contracted view. */ public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java index 89fcda949b5b..53f74161e7fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -72,10 +72,8 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> { // Bind/unbind with parameters mBinder.unbindContent(entry, row, contentToUnbind); - BindParams bindParams = new BindParams(); - bindParams.isMinimized = params.useMinimized(); - bindParams.usesIncreasedHeight = params.useIncreasedHeight(); - bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight(); + BindParams bindParams = new BindParams(params.useMinimized(), params.useIncreasedHeight(), + params.useIncreasedHeadsUpHeight(), params.getRedactionType()); boolean forceInflate = params.needsReinflation(); InflationCallback inflationCallback = new InflationCallback() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt index e702f10d7f50..fe2803bfc5d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt @@ -51,6 +51,7 @@ internal object SingleLineViewInflater { * notification, not for legacy messaging notifications * @param builder the recovered Notification Builder * @param systemUiContext the context of Android System UI + * @param redactText indicates if the text needs to be redacted * @return the inflated SingleLineViewModel */ @JvmStatic @@ -59,13 +60,21 @@ internal object SingleLineViewInflater { messagingStyle: MessagingStyle?, builder: Notification.Builder, systemUiContext: Context, + redactText: Boolean, ): SingleLineViewModel { if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) { return SingleLineViewModel(null, null, null) } peopleHelper.init(systemUiContext) var titleText = HybridGroupManager.resolveTitle(notification) - var contentText = HybridGroupManager.resolveText(notification) + var contentText = + if (redactText) { + systemUiContext.getString( + com.android.systemui.res.R.string.redacted_notification_single_line_text + ) + } else { + HybridGroupManager.resolveText(notification) + } if (messagingStyle == null) { return SingleLineViewModel( @@ -81,7 +90,7 @@ internal object SingleLineViewInflater { if (conversationTextData?.conversationTitle?.isNotEmpty() == true) { titleText = conversationTextData.conversationTitle } - if (conversationTextData?.conversationText?.isNotEmpty() == true) { + if (!redactText && conversationTextData?.conversationText?.isNotEmpty() == true) { contentText = conversationTextData.conversationText } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt index 42acd7bcdc8a..705845ff984c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt @@ -75,7 +75,7 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) : constraintSet.apply { if (SceneContainerFlag.isEnabled) { when (horizontalPosition) { - is HorizontalPosition.FloatAtEnd -> + is HorizontalPosition.FloatAtStart -> constrainWidth(nsslId, horizontalPosition.width) is HorizontalPosition.MiddleToEdge -> setGuidelinePercent(R.id.nssl_guideline, horizontalPosition.ratio) @@ -83,13 +83,13 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) : } } + connect(nsslId, START, startConstraintId, START, marginStart) if ( !SceneContainerFlag.isEnabled || - horizontalPosition !is HorizontalPosition.FloatAtEnd + horizontalPosition !is HorizontalPosition.FloatAtStart ) { - connect(nsslId, START, startConstraintId, START, marginStart) + connect(nsslId, END, PARENT_ID, END, marginEnd) } - connect(nsslId, END, PARENT_ID, END, marginEnd) connect(nsslId, BOTTOM, PARENT_ID, BOTTOM, marginBottom) connect(nsslId, TOP, PARENT_ID, TOP, marginTop) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index b81c71ebe19b..fc8c70fb8e9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -247,7 +247,7 @@ constructor( Split -> HorizontalPosition.MiddleToEdge(ratio = 0.5f) Dual -> if (isShadeLayoutWide) { - HorizontalPosition.FloatAtEnd( + HorizontalPosition.FloatAtStart( width = getDimensionPixelSize(R.dimen.shade_panel_width) ) } else { @@ -830,10 +830,10 @@ constructor( data class MiddleToEdge(val ratio: Float = 0.5f) : HorizontalPosition /** - * The container has a fixed [width] and is aligned to the end of the screen. In this - * layout, the start edge of the container is floating, i.e. unconstrained. + * The container has a fixed [width] and is aligned to the start of the screen. In this + * layout, the end edge of the container is floating, i.e. unconstrained. */ - data class FloatAtEnd(val width: Int) : HorizontalPosition + data class FloatAtStart(val width: Int) : HorizontalPosition } /** diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index a382cf921152..e08114f6c3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -21,10 +21,8 @@ import android.content.Context import android.hardware.devicestate.DeviceStateManager import android.os.PowerManager import android.provider.Settings -import androidx.core.view.OneShotPreDrawListener import com.android.internal.util.LatencyTracker import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.ToAodFoldTransitionInteractor @@ -125,11 +123,7 @@ constructor( private val shadeFoldAnimator: ShadeFoldAnimator get() { - return if (MigrateClocksToBlueprint.isEnabled) { - foldTransitionInteractor.get().foldAnimator - } else { - shadeViewController.shadeFoldAnimator - } + return foldTransitionInteractor.get().foldAnimator } private fun setAnimationState(playing: Boolean) { @@ -164,15 +158,7 @@ constructor( setAnimationState(playing = true) shadeFoldAnimator.prepareFoldToAodAnimation() - // We don't need to wait for the scrim as it is already displayed - // but we should wait for the initial animation preparations to be drawn - // (setting initial alpha/translation) - // TODO(b/254878364): remove this call to NPVC.getView() - if (!MigrateClocksToBlueprint.isEnabled) { - shadeFoldAnimator.view?.let { OneShotPreDrawListener.add(it, onReady) } - } else { - onReady.run() - } + onReady.run() } else { // No animation, call ready callback immediately onReady.run() @@ -252,7 +238,7 @@ constructor( if (isFolded) { foldToAodLatencyTracker.onFolded() } - } + }, ) /** @@ -272,6 +258,7 @@ constructor( latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD) } } + /** * Called once the Fold -> AOD animation is started. * diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt index 2e1f82d56fc4..70e342f3eefb 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt @@ -17,45 +17,28 @@ package com.android.systemui.volume.dialog.settings.ui.binder import android.view.View -import com.android.systemui.lifecycle.WindowLifecycleState -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.setSnapshotBinding -import com.android.systemui.lifecycle.viewModel +import android.widget.ImageButton import com.android.systemui.res.R import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.settings.ui.viewmodel.VolumeDialogSettingsButtonViewModel import javax.inject.Inject -import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @VolumeDialogScope class VolumeDialogSettingsButtonViewBinder @Inject -constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Factory) { +constructor(private val viewModel: VolumeDialogSettingsButtonViewModel) { - fun bind(view: View) { - with(view) { - val button = requireViewById<View>(R.id.volume_dialog_settings) - repeatWhenAttached { - viewModel( - traceName = "VolumeDialogViewBinder", - minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelFactory.create() }, - ) { viewModel -> - setSnapshotBinding { - viewModel.isVisible - .onEach { isVisible -> - visibility = if (isVisible) View.VISIBLE else View.GONE - } - .launchIn(this) + fun CoroutineScope.bind(view: View) { + val button = view.requireViewById<ImageButton>(R.id.volume_dialog_settings) + viewModel.isVisible + .onEach { isVisible -> button.visibility = if (isVisible) View.VISIBLE else View.GONE } + .launchIn(this) - button.setOnClickListener { viewModel.onButtonClicked() } - } + viewModel.icon.onEach { button.setImageDrawable(it) }.launchIn(this) - awaitCancellation() - } - } - } + button.setOnClickListener { viewModel.onButtonClicked() } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt index 015d773b2c02..03442dbcde66 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt @@ -14,27 +14,206 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.volume.dialog.settings.ui.viewmodel -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.ColorFilter +import android.graphics.drawable.Drawable +import android.media.session.PlaybackState +import androidx.annotation.ColorInt +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.LottieDrawable +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.SimpleColorFilter +import com.airbnb.lottie.model.KeyPath +import com.airbnb.lottie.value.LottieValueCallback +import com.android.internal.R as internalR +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.UiBackground +import com.android.systemui.lottie.await +import com.android.systemui.res.R +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.settings.domain.VolumeDialogSettingsButtonInteractor -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.shared.model.filterData +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.resume +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.runningFold +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.suspendCancellableCoroutine class VolumeDialogSettingsButtonViewModel -@AssistedInject -constructor(private val interactor: VolumeDialogSettingsButtonInteractor) { +@Inject +constructor( + @Application private val context: Context, + @UiBackground private val uiBgCoroutineContext: CoroutineContext, + @VolumeDialog private val coroutineScope: CoroutineScope, + mediaOutputInteractor: MediaOutputInteractor, + private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, + private val interactor: VolumeDialogSettingsButtonInteractor, +) { + + @SuppressLint("UseCompatLoadingForDrawables") + private val drawables: Flow<Drawables> = + flow { + val color = context.getColor(internalR.color.materialColorPrimary) + emit( + Drawables( + start = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_in) + .await() + .toDrawable { setColor(color) }, + playing = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_playing) + .await() + .toDrawable { + repeatCount = LottieDrawable.INFINITE + repeatMode = LottieDrawable.RESTART + setColor(color) + }, + stop = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_out) + .await() + .toDrawable { setColor(color) }, + idle = context.getDrawable(R.drawable.audio_bars_idle)!!, + ) + ) + } + .buffer() + .flowOn(uiBgCoroutineContext) + .stateIn(coroutineScope, SharingStarted.Eagerly, null) + .filterNotNull() val isVisible = interactor.isVisible + val icon: Flow<Drawable> = + mediaOutputInteractor.defaultActiveMediaSession + .filterData() + .flatMapLatest { session -> + if (session == null) { + flowOf(null) + } else { + mediaDeviceSessionInteractor.playbackState(session) + } + } + .runningFold(null) { playbackStates: PlaybackStates?, playbackState: PlaybackState? -> + val isCurrentActive = playbackState?.isActive ?: false + if (playbackStates != null && isCurrentActive == playbackState?.isActive) { + return@runningFold playbackStates + } + playbackStates?.copy( + isPreviousActive = playbackStates.isCurrentActive, + isCurrentActive = isCurrentActive, + ) ?: PlaybackStates(isPreviousActive = null, isCurrentActive = isCurrentActive) + } + .filterNotNull() + // only apply the most recent state if we wait for the animation. + .buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + // distinct again because the changed state might've been dropped by the buffer + .distinctUntilChangedBy { it.isCurrentActive } + .transform { emitDrawables(it) } + .runningFold(null) { previous: Drawable?, current: Drawable -> + // wait for the previous animation to finish before starting the new one + // this also waits for the current loop of the playing animation to finish + (previous as? LottieDrawable)?.awaitFinish() + (current as? LottieDrawable)?.start() + current + } + .filterNotNull() + + private suspend fun FlowCollector<Drawable>.emitDrawables(playbackStates: PlaybackStates) { + val animations = drawables.first() + val stateChanged = + playbackStates.isPreviousActive != null && + playbackStates.isPreviousActive != playbackStates.isCurrentActive + if (playbackStates.isCurrentActive) { + if (stateChanged) { + emit(animations.start) + } + emit(animations.playing) + } else { + if (stateChanged) { + emit(animations.stop) + } + emit(animations.idle) + } + } fun onButtonClicked() { interactor.onButtonClicked() } - @VolumeDialogScope - @AssistedFactory - interface Factory { + private data class PlaybackStates(val isPreviousActive: Boolean?, val isCurrentActive: Boolean) + + private data class Drawables( + val start: LottieDrawable, + val playing: LottieDrawable, + val stop: LottieDrawable, + val idle: Drawable, + ) +} + +private fun LottieComposition.toDrawable(setup: LottieDrawable.() -> Unit = {}): LottieDrawable = + LottieDrawable().also { drawable -> + drawable.composition = this + drawable.setup() + } - fun create(): VolumeDialogSettingsButtonViewModel +/** Suspends until current loop of the repeating animation is finished */ +private suspend fun LottieDrawable.awaitFinish() = suspendCancellableCoroutine { continuation -> + if (!isRunning) { + continuation.resume(Unit) + return@suspendCancellableCoroutine } + val listener = + object : AnimatorListenerAdapter() { + override fun onAnimationRepeat(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + + override fun onAnimationEnd(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + + override fun onAnimationCancel(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + } + addAnimatorListener(listener) + continuation.invokeOnCancellation { removeAnimatorListener(listener) } +} + +/** + * Overrides colors of the [LottieDrawable] to a specified [color] + * + * @see com.airbnb.lottie.LottieAnimationView + */ +private fun LottieDrawable.setColor(@ColorInt color: Int) { + val callback = LottieValueCallback<ColorFilter>(SimpleColorFilter(color)) + addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER, callback) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index f30524638150..faf06b942cab 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -26,7 +26,7 @@ import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory -import com.android.systemui.volume.dialog.ui.utils.awaitAnimation +import com.android.systemui.volume.dialog.ui.utils.suspendAnimate import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider import javax.inject.Inject @@ -84,5 +84,5 @@ private suspend fun Slider.setValueAnimated( interpolator = DecelerateInterpolator() addListener(jankListener) } - .awaitAnimation<Float> { value = it } + .suspendAnimate<Float> { value = it } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt index 10cf615ce0ce..5f124806dac7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt @@ -22,6 +22,7 @@ import android.animation.ValueAnimator import android.view.ViewPropertyAnimator import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation +import com.airbnb.lottie.LottieDrawable import kotlin.coroutines.resume import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine @@ -66,7 +67,7 @@ suspend fun ViewPropertyAnimator.suspendAnimate( * is cancelled. */ @Suppress("UNCHECKED_CAST") -suspend fun <T> ValueAnimator.awaitAnimation(onValueChanged: (T) -> Unit) { +suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) { suspendCancellableCoroutine { continuation -> addListener( object : AnimatorListenerAdapter() { @@ -103,6 +104,29 @@ suspend fun SpringAnimation.suspendAnimate( } } +/** + * Starts the animation and suspends until it's finished. Cancels the animation if the running + * coroutine is cancelled. + */ +suspend fun LottieDrawable.suspendAnimate() = suspendCancellableCoroutine { continuation -> + val listener = + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + continuation.resumeIfCan(Unit) + } + + override fun onAnimationCancel(animation: Animator) { + continuation.resumeIfCan(Unit) + } + } + addAnimatorListener(listener) + start() + continuation.invokeOnCancellation { + removeAnimatorListener(listener) + stop() + } +} + private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) { if (!isCancelled && !isCompleted) { resume(value) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt index 6e1ebc820b08..12e624cae4d4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt @@ -19,10 +19,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.media.session.MediaController import android.media.session.PlaybackState import com.android.settingslib.volume.data.repository.MediaControllerRepository +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,7 +38,7 @@ import kotlinx.coroutines.withContext /** Allows to observe and change [MediaDeviceSession] state. */ @OptIn(ExperimentalCoroutinesApi::class) -@VolumePanelScope +@SysUISingleton class MediaDeviceSessionInteractor @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index b3848a6d7817..2973e11c365d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -24,12 +24,13 @@ import androidx.annotation.WorkerThread import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.MediaControllerRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.Execution import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.shared.model.Result import com.android.systemui.volume.panel.shared.model.filterData import com.android.systemui.volume.panel.shared.model.wrapInResult @@ -54,13 +55,13 @@ import kotlinx.coroutines.withContext /** Provides observable models about the current media session state. */ @OptIn(ExperimentalCoroutinesApi::class) -@VolumePanelScope +@SysUISingleton class MediaOutputInteractor @Inject constructor( private val localMediaRepositoryFactory: LocalMediaRepositoryFactory, private val packageManager: PackageManager, - @VolumePanelScope private val coroutineScope: CoroutineScope, + @Application private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, private val mediaControllerInteractor: MediaControllerInteractor, @@ -77,7 +78,7 @@ constructor( .onStart { emit(activeSessions) } } .map { getMediaControllers(it) } - .stateIn(coroutineScope, SharingStarted.Eagerly, MediaControllers(null, null)) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), MediaControllers(null, null)) /** [MediaDeviceSessions] that contains currently active sessions. */ val activeMediaDeviceSessions: Flow<MediaDeviceSessions> = @@ -89,7 +90,11 @@ constructor( ) } .flowOn(backgroundCoroutineContext) - .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null)) + .stateIn( + coroutineScope, + SharingStarted.WhileSubscribed(), + MediaDeviceSessions(null, null), + ) /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */ val defaultActiveMediaSession: StateFlow<Result<MediaDeviceSession?>> = @@ -104,7 +109,7 @@ constructor( } .wrapInResult() .flowOn(backgroundCoroutineContext) - .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading()) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), Result.Loading()) private val localMediaRepository: Flow<LocalMediaRepository> = defaultActiveMediaSession diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 32fa160fc29f..21dd5bc068f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -344,7 +344,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { canShowWhileLocked = canShowWhileLocked, ) } else { - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } underTest.onQuickAffordanceTriggered( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index eb19a9c2528d..1ce128c2403a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -347,7 +347,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { canShowWhileLocked = canShowWhileLocked, ) } else { - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } underTest.onQuickAffordanceTriggered( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index b26f0a6e71a3..782b24825bcf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -557,6 +557,13 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { } @Test + public void startActivityForDialog_always_startActivityWithoutDismissShade() { + mInternetDialogController.startActivityForDialog(mock(Intent.class)); + + verify(mActivityStarter).startActivity(any(Intent.class), eq(false) /* dismissShade */); + } + + @Test public void launchWifiDetailsSetting_withNoWifiEntryKey_doNothing() { mInternetDialogController.launchWifiDetailsSetting(null /* key */, mDialogLaunchView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt index 503fa789cb80..1eb88c5a5616 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt @@ -89,6 +89,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { messagingStyle = null, builder = notificationBuilder, systemUiContext = context, + redactText = false, ) // WHEN: binds the viewHolder @@ -149,6 +150,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { messagingStyle = style, builder = notificationBuilder, systemUiContext = context, + redactText = false, ) // WHEN: binds the view SingleLineViewBinder.bind(viewModel, view) @@ -197,6 +199,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { messagingStyle = null, builder = notificationBuilder, systemUiContext = context, + redactText = false, ) // WHEN: binds the view with the view model SingleLineViewBinder.bind(viewModel, view) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt index d3666321c8e4..ef70e277832e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt @@ -379,7 +379,8 @@ class SingleLineViewInflaterTest : SysuiTestCase() { this, if (isConversation) messagingStyle else null, builder, - context + context, + false ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt new file mode 100644 index 000000000000..57c8fd066ea8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.scene.domain.interactor.sceneInteractor + +val Kosmos.communalBackActionInteractor by + Kosmos.Fixture { + CommunalBackActionInteractor( + communalInteractor = communalInteractor, + communalSceneInteractor = communalSceneInteractor, + sceneInteractor = sceneInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt index 548b5646f5d4..5d206691b520 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt @@ -30,7 +30,7 @@ class FakeKeyguardQuickAffordanceConfig( override val pickerIconResourceId: Int = 0, ) : KeyguardQuickAffordanceConfig { - var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled + var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled(false) private val _lockScreenState = MutableStateFlow<KeyguardQuickAffordanceConfig.LockScreenState>( @@ -41,9 +41,7 @@ class FakeKeyguardQuickAffordanceConfig( override fun pickerName(): String = pickerName - override fun onTriggered( - expandable: Expandable?, - ): OnTriggeredResult { + override fun onTriggered(expandable: Expandable?): OnTriggeredResult { return onTriggeredResult } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt new file mode 100644 index 000000000000..d857157137b6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.flow.Flow + +val Kosmos.keyguardQuickAffordanceHapticViewModelFactory by + Kosmos.Fixture { + object : KeyguardQuickAffordanceHapticViewModel.Factory { + override fun create( + quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel> + ): KeyguardQuickAffordanceHapticViewModel = + KeyguardQuickAffordanceHapticViewModel( + quickAffordanceViewModel, + keyguardQuickAffordanceInteractor, + ) + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index e47310727905..abbfa93edd17 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -87,7 +87,6 @@ val Kosmos.keyguardRootViewModel by Fixture { primaryBouncerToLockscreenTransitionViewModel, screenOffAnimationController = screenOffAnimationController, aodBurnInViewModel = aodBurnInViewModel, - aodAlphaViewModel = aodAlphaViewModel, shadeInteractor = shadeInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt index ab1c1818bf80..aa29808bd9ee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt @@ -45,7 +45,6 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) : other ?: return } check("icon").that(actual.icon).isEqualTo(other.icon) - check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes) check("label").that(actual.label).isEqualTo(other.label) check("activationState").that(actual.activationState).isEqualTo(other.activationState) check("secondaryLabel").that(actual.secondaryLabel).isEqualTo(other.secondaryLabel) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt new file mode 100644 index 000000000000..62cdc87f980f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope + +val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by + Kosmos.Fixture { StatusBarPopupChipsViewModel(testScope.backgroundScope) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt index a3572754ab19..f31697e82a45 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt @@ -70,6 +70,14 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { } } + fun getContentObservers(uri: Uri, userHandle: Int): List<ContentObserver> { + if (userHandle == UserHandle.USER_ALL) { + return contentObserversAllUsers[uri.toString()] ?: listOf() + } else { + return contentObservers[SettingsKey(userHandle, uri.toString())] ?: listOf() + } + } + override fun getContentResolver(): ContentResolver { throw UnsupportedOperationException("FakeSettings.getContentResolver is not implemented") } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 3441d94facda..9ceca5d1dbfe 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1589,7 +1589,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * lock because this calls out to WindowManagerService. */ void addWindowTokensForAllDisplays() { - final Display[] displays = mDisplayManager.getDisplays(); + Display[] displays = {}; + final long identity = Binder.clearCallingIdentity(); + try { + displays = mDisplayManager.getDisplays(); + } finally { + Binder.restoreCallingIdentity(identity); + } for (int i = 0; i < displays.length; i++) { final int displayId = displays[i].getDisplayId(); addWindowTokenForDisplay(displayId); @@ -1625,7 +1631,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } public void onRemoved() { - final Display[] displays = mDisplayManager.getDisplays(); + Display[] displays = {}; + final long identity = Binder.clearCallingIdentity(); + try { + displays = mDisplayManager.getDisplays(); + } finally { + Binder.restoreCallingIdentity(identity); + } for (int i = 0; i < displays.length; i++) { final int displayId = displays[i].getDisplayId(); onDisplayRemoved(displayId); diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 4944cafeb83d..4b8770b3cd35 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; @@ -31,6 +32,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean; import android.annotation.NonNull; +import android.app.ActivityManagerInternal; import android.app.ActivityThread; import android.app.ForegroundServiceTypePolicy; import android.content.ComponentName; @@ -55,6 +57,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; import dalvik.annotation.optimization.NeverCompile; @@ -181,6 +184,12 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION = "follow_up_oomadj_update_wait_duration"; + /* + * Oom score cutoff beyond which any process that does not have the CPU_TIME capability will be + * frozen. + */ + static final String KEY_FREEZER_CUTOFF_ADJ = "freezer_cutoff_adj"; + private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024; private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true; private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000; @@ -267,6 +276,9 @@ final class ActivityManagerConstants extends ContentObserver { */ private static final long DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION = 1000L; + /** The default value to {@link #KEY_FREEZER_CUTOFF_ADJ} */ + private static final int DEFAULT_FREEZER_CUTOFF_ADJ = ProcessList.CACHED_APP_MIN_ADJ; + /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} */ @@ -1171,6 +1183,14 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION; /** + * The cutoff adj for the freezer, app processes with adj greater than this value will be + * eligible for the freezer. + * + * @see #KEY_FREEZER_CUTOFF_ADJ + */ + public int FREEZER_CUTOFF_ADJ = DEFAULT_FREEZER_CUTOFF_ADJ; + + /** * Indicates whether PSS profiling in AppProfiler is disabled or not. */ static final String KEY_DISABLE_APP_PROFILER_PSS_PROFILING = @@ -1194,6 +1214,7 @@ final class ActivityManagerConstants extends ContentObserver { new OnPropertiesChangedListener() { @Override public void onPropertiesChanged(Properties properties) { + boolean oomAdjusterConfigUpdated = false; for (String name : properties.getKeyset()) { if (name == null) { return; @@ -1372,6 +1393,11 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE: updateUseTieredCachedAdj(); break; + case KEY_FREEZER_CUTOFF_ADJ: + FREEZER_CUTOFF_ADJ = properties.getInt(KEY_FREEZER_CUTOFF_ADJ, + DEFAULT_FREEZER_CUTOFF_ADJ); + oomAdjusterConfigUpdated = true; + break; case KEY_DISABLE_APP_PROFILER_PSS_PROFILING: updateDisableAppProfilerPssProfiling(); break; @@ -1389,6 +1415,13 @@ final class ActivityManagerConstants extends ContentObserver { break; } } + if (oomAdjusterConfigUpdated) { + final ActivityManagerInternal ami = LocalServices.getService( + ActivityManagerInternal.class); + if (ami != null) { + ami.updateOomAdj(OOM_ADJ_REASON_RECONFIGURATION); + } + } } }; @@ -2534,6 +2567,9 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ); pw.print("="); pw.println(ENABLE_NEW_OOMADJ); + pw.print(" "); pw.print(KEY_FREEZER_CUTOFF_ADJ); + pw.print("="); pw.println(FREEZER_CUTOFF_ADJ); + pw.print(" "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING); pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 2f5362f53361..d335529a006a 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -25,9 +25,11 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FOLLOW_UP; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; @@ -203,6 +205,10 @@ public class CachedAppOptimizer { FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE; static final int UNFREEZE_REASON_COMPONENT_DISABLED = FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED; + static final int UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_FOLLOW_UP; + static final int UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_RECONFIGURATION; @IntDef(prefix = {"UNFREEZE_REASON_"}, value = { UNFREEZE_REASON_NONE, @@ -234,6 +240,8 @@ public class CachedAppOptimizer { UNFREEZE_REASON_EXECUTING_SERVICE, UNFREEZE_REASON_RESTRICTION_CHANGE, UNFREEZE_REASON_COMPONENT_DISABLED, + UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP, + UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION, }) @Retention(RetentionPolicy.SOURCE) public @interface UnfreezeReason {} @@ -2451,8 +2459,8 @@ public class CachedAppOptimizer { synchronized (mAm.mPidsSelfLocked) { pr = mAm.mPidsSelfLocked.get(blocked); } - if (pr != null - && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) { + if (pr != null && pr.mState.getCurAdj() + < mAm.mConstants.FREEZER_CUTOFF_ADJ) { Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks " + pr.processName + " (" + blocked + ")"); // Found at least one blocked non-cached process @@ -2539,6 +2547,10 @@ public class CachedAppOptimizer { return UNFREEZE_REASON_RESTRICTION_CHANGE; case OOM_ADJ_REASON_COMPONENT_DISABLED: return UNFREEZE_REASON_COMPONENT_DISABLED; + case OOM_ADJ_REASON_FOLLOW_UP: + return UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP; + case OOM_ADJ_REASON_RECONFIGURATION: + return UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION; default: return UNFREEZE_REASON_NONE; } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index aadf6f61956c..9c569db99797 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -55,6 +55,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; @@ -105,7 +106,6 @@ import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS; import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ; import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ; -import static com.android.server.am.ProcessList.FREEZER_CUTOFF_ADJ; import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; import static com.android.server.am.ProcessList.HOME_APP_ADJ; import static com.android.server.am.ProcessList.INVALID_ADJ; @@ -232,6 +232,8 @@ public class OomAdjuster { return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED; case OOM_ADJ_REASON_FOLLOW_UP: return AppProtoEnums.OOM_ADJ_REASON_FOLLOW_UP; + case OOM_ADJ_REASON_RECONFIGURATION: + return AppProtoEnums.OOM_ADJ_REASON_RECONFIGURATION; default: return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO; } @@ -288,6 +290,8 @@ public class OomAdjuster { return OOM_ADJ_REASON_METHOD + "_componentDisabled"; case OOM_ADJ_REASON_FOLLOW_UP: return OOM_ADJ_REASON_METHOD + "_followUp"; + case OOM_ADJ_REASON_RECONFIGURATION: + return OOM_ADJ_REASON_METHOD + "_reconfiguration"; default: return "_unknown"; } @@ -4079,7 +4083,7 @@ public class OomAdjuster { } // Reasons to freeze: - if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) { + if (proc.mState.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ) { // Oomscore is in a high enough state, it is safe to freeze. return true; } @@ -4098,9 +4102,8 @@ public class OomAdjuster { final ProcessCachedOptimizerRecord opt = app.mOptRecord; final ProcessStateRecord state = app.mState; if (Flags.traceUpdateAppFreezeStateLsp()) { - final boolean oomAdjChanged = - (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ) - || oldOomAdj == UNKNOWN_ADJ; + final boolean oomAdjChanged = (state.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ + ^ oldOomAdj >= mConstants.FREEZER_CUTOFF_ADJ) || oldOomAdj == UNKNOWN_ADJ; final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq; final boolean hasCpuCapability = (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability()) diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 70febcd63455..bddde9d589f3 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -383,12 +383,6 @@ public final class ProcessList { private static final long LMKD_RECONNECT_DELAY_MS = 1000; /** - * The cuttoff adj for the freezer, app processes with adj greater than this value will be - * eligible for the freezer. - */ - static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ; - - /** * Apps have no access to the private data directories of any other app, even if the other * app has made them world-readable. */ diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 98f738c38d63..49149e1fa415 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1699,7 +1699,7 @@ class ProcessRecord implements WindowProcessListener { return mService.mOomAdjuster.mCachedAppOptimizer.useFreezer() && !mOptRecord.isFreezeExempt() && !mOptRecord.shouldNotFreeze() - && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ; + && mState.getCurAdj() >= mService.mConstants.FREEZER_CUTOFF_ADJ; } public void forEachConnectionHost(Consumer<ProcessRecord> consumer) { diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 87f87c76725e..c82933c5069e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -158,6 +158,7 @@ public class SettingsToPropertiesMapper { "aoc", "app_widgets", "arc_next", + "art_cloud", "art_mainline", "art_performance", "attack_tools", diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 2985ad330bc6..5740e16dc886 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2400,6 +2400,10 @@ public final class DisplayManagerService extends SystemService { sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); applyDisplayChangedLocked(display); + + if (mDisplayTopologyCoordinator != null) { + mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked()); + } } private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) { diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index 5b78726cc421..461a9f3f2a0d 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -85,13 +85,26 @@ class DisplayTopologyCoordinator { } /** + * Update the topology with display changes. + * @param info The new display info + */ + void onDisplayChanged(DisplayInfo info) { + synchronized (mSyncRoot) { + if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) { + sendTopologyUpdateLocked(); + } + } + } + + /** * Remove a display from the topology. * @param displayId The logical display ID */ void onDisplayRemoved(int displayId) { synchronized (mSyncRoot) { - mTopology.removeDisplay(displayId); - sendTopologyUpdateLocked(); + if (mTopology.removeDisplay(displayId)) { + sendTopologyUpdateLocked(); + } } } diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java new file mode 100644 index 000000000000..6cab60c05b8e --- /dev/null +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java @@ -0,0 +1,97 @@ +/* + * 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.location.provider.proxy; + +import android.annotation.Nullable; +import android.content.Context; +import android.location.provider.GnssAssistanceProviderBase; +import android.location.provider.IGnssAssistanceCallback; +import android.location.provider.IGnssAssistanceProvider; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.server.servicewatcher.CurrentUserServiceSupplier; +import com.android.server.servicewatcher.ServiceWatcher; + +/** + * Proxy for IGnssAssitanceProvider implementations. + */ +public class ProxyGnssAssistanceProvider { + + private static final String TAG = "GnssAssistanceProxy"; + /** + * Creates and registers this proxy. If no suitable service is available for the proxy, returns + * null. + */ + @Nullable + public static ProxyGnssAssistanceProvider createAndRegister(Context context) { + ProxyGnssAssistanceProvider proxy = new ProxyGnssAssistanceProvider(context); + if (proxy.register()) { + return proxy; + } else { + return null; + } + } + + private final ServiceWatcher mServiceWatcher; + + private ProxyGnssAssistanceProvider(Context context) { + mServiceWatcher = + ServiceWatcher.create( + context, + TAG, + CurrentUserServiceSupplier.createFromConfig( + context, + GnssAssistanceProviderBase.ACTION_GNSS_ASSISTANCE_PROVIDER, + com.android.internal.R.bool.config_enableGnssAssistanceOverlay, + com.android.internal.R.string + .config_gnssAssistanceProviderPackageName), + /* serviceListener= */ null); + } + + private boolean register() { + boolean resolves = mServiceWatcher.checkServiceResolves(); + if (resolves) { + mServiceWatcher.register(); + } + return resolves; + } + + /** + * Request GNSS assistance. + */ + public void request(IGnssAssistanceCallback callback) { + mServiceWatcher.runOnBinder( + new ServiceWatcher.BinderOperation() { + @Override + public void run(IBinder binder) throws RemoteException { + IGnssAssistanceProvider.Stub.asInterface(binder).request(callback); + } + + @Override + public void onError(Throwable t) { + try { + Log.w(TAG, "Error on requesting GnssAssistance: " + t); + callback.onError(); + } catch (RemoteException e) { + // ignore + } + } + }); + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 0438a1bac662..0d6e502cf965 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -179,8 +179,29 @@ abstract class MediaRoute2Provider { void onProviderStateChanged(@Nullable MediaRoute2Provider provider); void onSessionCreated(@NonNull MediaRoute2Provider provider, long requestId, @Nullable RoutingSessionInfo sessionInfo); - void onSessionUpdated(@NonNull MediaRoute2Provider provider, - @NonNull RoutingSessionInfo sessionInfo); + + /** + * Called when there's a session info change. + * + * <p>If the provided {@code sessionInfo} has a null {@link + * RoutingSessionInfo#getClientPackageName()}, that means that it's applicable to all + * packages. We call this type of routing session "global". This is typically used for + * system provided {@link RoutingSessionInfo}. However, some applications may be exempted + * from the global routing sessions, because their media is being routed using a session + * different from the global routing session. + * + * @param provider The provider that owns the session that changed. + * @param sessionInfo The new {@link RoutingSessionInfo}. + * @param packageNamesWithRoutingSessionOverrides The names of packages that are not + * affected by global session changes. This set may only be non-empty when the {@code + * sessionInfo} is for the global session, and therefore has no {@link + * RoutingSessionInfo#getClientPackageName()}. + */ + void onSessionUpdated( + @NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo, + Set<String> packageNamesWithRoutingSessionOverrides); + void onSessionReleased(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 80d3c5c5c5ec..d6f7d3bdd4a4 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -16,6 +16,7 @@ package com.android.server.media; +import static android.media.MediaRoute2ProviderService.REASON_REJECTED; import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -499,6 +500,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { synchronized (mLock) { var systemMediaSessionCallback = mRequestIdToSystemSessionRequest.get(requestId); if (systemMediaSessionCallback != null) { + mRequestIdToSystemSessionRequest.remove(requestId); mSystemSessionCallbacks.put(newSession.getOriginalId(), systemMediaSessionCallback); systemMediaSessionCallback.onSessionUpdate(newSession); return; @@ -674,7 +676,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { private void dispatchSessionUpdated(RoutingSessionInfo session) { mHandler.sendMessage( - obtainMessage(mCallback::onSessionUpdated, this, session)); + obtainMessage( + mCallback::onSessionUpdated, + this, + session, + /* packageNamesWithRoutingSessionOverrides= */ Set.of())); } private void dispatchSessionReleased(RoutingSessionInfo session) { @@ -717,6 +723,19 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { for (RoutingSessionInfo sessionInfo : mSessionInfos) { mCallback.onSessionReleased(this, sessionInfo); } + if (Flags.enableMirroringInMediaRouter2()) { + for (var callback : mSystemSessionCallbacks.values()) { + callback.onSessionReleased(); + } + mSystemSessionCallbacks.clear(); + int requestsSize = mRequestIdToSystemSessionRequest.size(); + for (int i = 0; i < requestsSize; i++) { + var callback = mRequestIdToSystemSessionRequest.valueAt(i); + var requestId = mRequestIdToSystemSessionRequest.keyAt(i); + callback.onRequestFailed(requestId, REASON_REJECTED); + } + mSystemSessionCallbacks.clear(); + } mSessionInfos.clear(); mReleasingSessions.clear(); mRequestIdToSessionCreationRequest.clear(); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 83ac05d9d4c3..5e6737a485af 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -2634,10 +2634,17 @@ class MediaRouter2ServiceImpl { } @Override - public void onSessionUpdated(@NonNull MediaRoute2Provider provider, - @NonNull RoutingSessionInfo sessionInfo) { - sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler, - this, provider, sessionInfo)); + public void onSessionUpdated( + @NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo, + Set<String> packageNamesWithRoutingSessionOverrides) { + sendMessage( + PooledLambda.obtainMessage( + UserHandler::onSessionInfoChangedOnHandler, + this, + provider, + sessionInfo, + packageNamesWithRoutingSessionOverrides)); } @Override @@ -3148,10 +3155,31 @@ class MediaRouter2ServiceImpl { toOriginalRequestId(uniqueRequestId), sessionInfo); } - private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, - @NonNull RoutingSessionInfo sessionInfo) { + /** + * Implementation of {@link MediaRoute2Provider.Callback#onSessionUpdated}. + * + * <p>Must run on the thread that corresponds to this {@link UserHandler}. + */ + private void onSessionInfoChangedOnHandler( + @NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo, + Set<String> packageNamesWithRoutingSessionOverrides) { List<ManagerRecord> managers = getManagerRecords(); for (ManagerRecord manager : managers) { + if (Flags.enableMirroringInMediaRouter2()) { + String targetPackageName = manager.mTargetPackageName; + boolean skipDueToOverride = + targetPackageName != null + && packageNamesWithRoutingSessionOverrides.contains( + targetPackageName); + boolean sessionIsForTargetPackage = + TextUtils.isEmpty(sessionInfo.getClientPackageName()) // is global. + || TextUtils.equals( + targetPackageName, sessionInfo.getClientPackageName()); + if (skipDueToOverride || !sessionIsForTargetPackage) { + continue; + } + } manager.notifySessionUpdated(sessionInfo); } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 4aec3678af8b..60fced1e0c51 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -62,7 +62,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION"; private final AudioManager mAudioManager; - private final Handler mHandler; + protected final Handler mHandler; private final Context mContext; private final UserHandle mUser; @@ -116,7 +116,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { () -> { publishProviderState(); if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } }); @@ -129,7 +129,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { () -> { publishProviderState(); if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } })); } @@ -161,7 +161,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void setCallback(Callback callback) { super.setCallback(callback); notifyProviderState(); - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } @Override @@ -296,7 +296,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses() && updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } } @@ -643,7 +643,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { notifyProviderState(); } - void notifySessionInfoUpdated() { + void notifyGlobalSessionInfoUpdated() { if (mCallback == null) { return; } @@ -656,7 +656,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { sessionInfo = mSessionInfos.get(0); } - mCallback.onSessionUpdated(this, sessionInfo); + mCallback.onSessionUpdated( + this, sessionInfo, /* packageNamesWithRoutingSessionOverrides= */ Set.of()); } @Override diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java index a27a14b87d53..8931e3a1426e 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java @@ -44,6 +44,7 @@ import com.android.server.media.MediaRoute2ProviderServiceProxy.SystemMediaSessi import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; /** @@ -82,7 +83,7 @@ import java.util.stream.Stream; /** Maps request ids to pending session creation callbacks. */ @GuardedBy("mLock") - private final LongSparseArray<PendingSessionCreationCallbackImpl> mPendingSessionCreations = + private final LongSparseArray<SystemMediaSessionCallbackImpl> mPendingSessionCreations = new LongSparseArray<>(); private static final ComponentName COMPONENT_NAME = @@ -157,7 +158,7 @@ import java.util.stream.Stream; } } var pendingCreationCallback = - new PendingSessionCreationCallbackImpl( + new SystemMediaSessionCallbackImpl( targetProviderProxyId, requestId, clientPackageName); mPendingSessionCreations.put(requestId, pendingCreationCallback); targetProviderProxyRecord.requestCreateSystemMediaSession( @@ -242,7 +243,7 @@ import java.util.stream.Stream; } updateSessionInfo(); notifyProviderState(); - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } @Override @@ -252,7 +253,7 @@ import java.util.stream.Stream; updateProviderInfo(); } updateSessionInfo(); - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } /** @@ -302,6 +303,43 @@ import java.util.stream.Stream; setProviderState(builder.build()); } + @Override + /* package */ void notifyGlobalSessionInfoUpdated() { + if (mCallback == null) { + return; + } + + RoutingSessionInfo sessionInfo; + Set<String> packageNamesWithRoutingSessionOverrides; + synchronized (mLock) { + if (mSessionInfos.isEmpty()) { + return; + } + packageNamesWithRoutingSessionOverrides = mPackageNameToSessionRecord.keySet(); + sessionInfo = mSessionInfos.getFirst(); + } + + mCallback.onSessionUpdated(this, sessionInfo, packageNamesWithRoutingSessionOverrides); + } + + private void onSessionOverrideUpdated(RoutingSessionInfo sessionInfo) { + // TODO: b/362507305 - Consider adding routes from other provider services. This is not a + // trivial change because a provider1-route to provider2-route transfer has seemingly two + // possible approachies. Either we first release the current session and then create the new + // one, in which case the audio is briefly going to leak through the system route. On the + // other hand, if we first create the provider2 session, then there will be a period during + // which there will be two overlapping routing policies asking for the exact same media + // stream. + var builder = new RoutingSessionInfo.Builder(sessionInfo); + mLastSystemProviderInfo.getRoutes().stream() + .map(MediaRoute2Info::getOriginalId) + .forEach(builder::addTransferableRoute); + mCallback.onSessionUpdated( + /* provider= */ this, + builder.build(), + /* packageNamesWithRoutingSessionOverrides= */ Set.of()); + } + /** * Equivalent to {@link #asSystemRouteId}, except it takes a unique route id instead of a * original id. @@ -432,13 +470,15 @@ import java.util.stream.Stream; } } - private class PendingSessionCreationCallbackImpl implements SystemMediaSessionCallback { + private class SystemMediaSessionCallbackImpl implements SystemMediaSessionCallback { private final String mProviderId; private final long mRequestId; private final String mClientPackageName; + // Accessed only on mHandler. + @Nullable private SystemMediaSessionRecord mSessionRecord; - private PendingSessionCreationCallbackImpl( + private SystemMediaSessionCallbackImpl( String providerId, long requestId, String clientPackageName) { mProviderId = providerId; mRequestId = requestId; @@ -446,27 +486,51 @@ import java.util.stream.Stream; } @Override - public void onSessionUpdate(RoutingSessionInfo sessionInfo) { - SystemMediaSessionRecord systemMediaSessionRecord = - new SystemMediaSessionRecord(mProviderId, sessionInfo); - synchronized (mLock) { - mPackageNameToSessionRecord.put(mClientPackageName, systemMediaSessionRecord); - mPendingSessionCreations.remove(mRequestId); - } + public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) { + mHandler.post( + () -> { + if (mSessionRecord != null) { + mSessionRecord.onSessionUpdate(sessionInfo); + } + SystemMediaSessionRecord systemMediaSessionRecord = + new SystemMediaSessionRecord(mProviderId, sessionInfo); + RoutingSessionInfo translatedSession; + synchronized (mLock) { + mSessionRecord = systemMediaSessionRecord; + mPackageNameToSessionRecord.put( + mClientPackageName, systemMediaSessionRecord); + mPendingSessionCreations.remove(mRequestId); + translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo; + } + onSessionOverrideUpdated(translatedSession); + }); } @Override public void onRequestFailed(long requestId, @Reason int reason) { - synchronized (mLock) { - mPendingSessionCreations.remove(mRequestId); - } - notifyRequestFailed(requestId, reason); + mHandler.post( + () -> { + if (mSessionRecord != null) { + mSessionRecord.onRequestFailed(requestId, reason); + } + synchronized (mLock) { + mPendingSessionCreations.remove(mRequestId); + } + notifyRequestFailed(requestId, reason); + }); } @Override public void onSessionReleased() { - // Unexpected. The session hasn't yet been created. - throw new IllegalStateException(); + mHandler.post( + () -> { + if (mSessionRecord != null) { + mSessionRecord.onSessionReleased(); + } else { + // Should never happen. The session hasn't yet been created. + throw new IllegalStateException(); + } + }); } } @@ -494,12 +558,13 @@ import java.util.stream.Stream; } @Override - public void onSessionUpdate(RoutingSessionInfo sessionInfo) { + public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) { + RoutingSessionInfo translatedSessionInfo = mTranslatedSessionInfo; synchronized (mLock) { mSourceSessionInfo = sessionInfo; mTranslatedSessionInfo = asSystemProviderSession(sessionInfo); } - notifySessionInfoUpdated(); + onSessionOverrideUpdated(translatedSessionInfo); } @Override @@ -512,7 +577,7 @@ import java.util.stream.Stream; synchronized (mLock) { removeSelfFromSessionMap(); } - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } @GuardedBy("SystemMediaRoute2Provider2.this.mLock") @@ -536,6 +601,7 @@ import java.util.stream.Stream; var builder = new RoutingSessionInfo.Builder(session) .setProviderId(mUniqueId) + .setSystemSession(true) .clearSelectedRoutes() .clearSelectableRoutes() .clearDeselectableRoutes() diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 0c3c46c75eee..7f88e7463208 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -479,6 +479,7 @@ public class Notifier { case PowerManager.PARTIAL_WAKE_LOCK: return BatteryStats.WAKE_TYPE_PARTIAL; + case PowerManager.FULL_WAKE_LOCK: case PowerManager.SCREEN_DIM_WAKE_LOCK: case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: return BatteryStats.WAKE_TYPE_FULL; @@ -503,6 +504,31 @@ public class Notifier { } } + @VisibleForTesting + int getWakelockMonitorTypeForLogging(int flags) { + switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK, PowerManager.SCREEN_DIM_WAKE_LOCK, + PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + return PowerManager.FULL_WAKE_LOCK; + case PowerManager.DRAW_WAKE_LOCK: + return PowerManager.DRAW_WAKE_LOCK; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + if (mSuspendWhenScreenOffDueToProximityConfig) { + return -1; + } + return PowerManager.PARTIAL_WAKE_LOCK; + case PowerManager.PARTIAL_WAKE_LOCK: + return PowerManager.PARTIAL_WAKE_LOCK; + case PowerManager.DOZE_WAKE_LOCK: + // Doze wake locks are an internal implementation detail of the + // communication between dream manager service and power manager + // service. They have no additive battery impact. + return -1; + default: + return -1; + } + } + /** * Notifies that the device is changing wakefulness. * This function may be called even if the previous change hasn't finished in @@ -1288,7 +1314,7 @@ public class Notifier { if (mBatteryStatsInternal == null) { return; } - final int type = flags & PowerManager.WAKE_LOCK_LEVEL_MASK; + final int type = getWakelockMonitorTypeForLogging(flags); if (workSource == null || workSource.isEmpty()) { final int mappedUid = mBatteryStatsInternal.getOwnerUid(ownerUid); mFrameworkStatsLogger.wakelockStateChanged(mappedUid, tag, type, eventType); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 83b273c04648..a01fa48f2af2 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -10355,7 +10355,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (!isVisibleRequested()) { // TODO(b/294925498): Remove this finishing check once we have accurate ready tracking. - if (task != null && task.getPausingActivity() == this) { + if (task != null && task.getPausingActivity() == this + // Display is asleep, so nothing will be visible anyways. + && !mDisplayContent.isSleeping()) { // Visibility of starting activities isn't calculated until pause-complete, so if // this is not paused yet, don't consider it ready. return false; diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java index caff96ba4a9f..4fac81b06680 100644 --- a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java @@ -35,8 +35,6 @@ import android.annotation.NonNull; import android.content.res.Configuration; import android.graphics.Rect; -import com.android.window.flags.Flags; - /** * Encapsulate overrides and configurations about app compat reachability. */ @@ -157,33 +155,27 @@ class AppCompatReachabilityOverrides { } /** - * @return {@value true} if the vertical reachability should be allowed in case of + * @return {@code true} if the vertical reachability should be allowed in case of * thin letterboxing. */ boolean allowVerticalReachabilityForThinLetterbox() { - if (!Flags.disableThinLetterboxingPolicy()) { - return true; - } // When the flag is enabled we allow vertical reachability only if the // app is not thin letterboxed vertically. return !isVerticalThinLetterboxed(); } /** - * @return {@value true} if the horizontal reachability should be enabled in case of + * @return {@code true} if the horizontal reachability should be enabled in case of * thin letterboxing. */ boolean allowHorizontalReachabilityForThinLetterbox() { - if (!Flags.disableThinLetterboxingPolicy()) { - return true; - } // When the flag is enabled we allow horizontal reachability only if the // app is not thin pillarboxed. return !isHorizontalThinLetterboxed(); } /** - * @return {@value true} if the resulting app is letterboxed in a way defined as thin. + * @return {@code true} if the resulting app is letterboxed in a way defined as thin. */ boolean isVerticalThinLetterboxed() { final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx(); @@ -200,7 +192,7 @@ class AppCompatReachabilityOverrides { } /** - * @return {@value true} if the resulting app is pillarboxed in a way defined as thin. + * @return {@code true} if the resulting app is pillarboxed in a way defined as thin. */ boolean isHorizontalThinLetterboxed() { final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx(); diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 0369a0ff4c76..9f88bc952351 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -164,15 +164,13 @@ final class AppCompatUtils { appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap()); + appCompatTaskInfo.topActivityAppBounds.set(getAppBounds(top)); final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed(); appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed); if (isTopActivityLetterboxed) { final Rect bounds = top.getBounds(); - final Rect appBounds = getAppBounds(top); appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); - appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); - appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); // TODO(b/379824541) Remove duplicate information. appCompatTaskInfo.topActivityLetterboxBounds = bounds; // We need to consider if letterboxed or pillarboxed. @@ -281,8 +279,7 @@ final class AppCompatUtils { info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET; - info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET; - info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET; + info.topActivityAppBounds.setEmpty(); info.topActivityLetterboxBounds = null; info.cameraCompatTaskInfo.freeformCameraCompatMode = CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9d9c53dfe0f4..db62cebf7603 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2945,7 +2945,7 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent dc = mRoot.getDisplayContent(displayId); if (dc == null) { if (callingPid != MY_PID) { - throw new WindowManager.InvalidDisplayException( + throw new IllegalArgumentException( "attachWindowContextToDisplayContent: trying to attach to a" + " non-existing display:" + displayId); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 60130d1f97be..f10b7b9a95a4 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -91,6 +91,7 @@ import android.server.ServerProtoEnums; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; +import android.tracing.perfetto.InitArguments; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Dumpable; @@ -792,6 +793,12 @@ public final class SystemServer implements Dumpable { private void run() { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); try { + if (android.tracing.Flags.systemServerLargePerfettoShmemBuffer()) { + // Explicitly initialize a 4 MB shmem buffer for Perfetto producers (b/382369925) + android.tracing.perfetto.Producer.init(new InitArguments( + InitArguments.PERFETTO_BACKEND_SYSTEM, 4 * 1024)); + } + t.traceBegin("InitBeforeStartServices"); // Record the process start information in sys props. @@ -3114,10 +3121,10 @@ public final class SystemServer implements Dumpable { if (com.android.ranging.flags.Flags.rangingStackEnabled()) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB) || context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_WIFI_RTT) + PackageManager.FEATURE_WIFI_AWARE) || (com.android.ranging.flags.Flags.rangingCsEnabled() && context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING))) { + PackageManager.FEATURE_BLUETOOTH_LE))) { t.traceBegin("RangingService"); // TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next. try { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt index 5d427139a857..c65024f8f9d5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -26,6 +26,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -43,7 +44,7 @@ class DisplayTopologyCoordinatorTest { @Before fun setUp() { - displayInfo.displayId = 2 + displayInfo.displayId = Display.DEFAULT_DISPLAY displayInfo.logicalWidth = 300 displayInfo.logicalHeight = 200 displayInfo.logicalDensityDpi = 100 @@ -90,6 +91,44 @@ class DisplayTopologyCoordinatorTest { } @Test + fun updateDisplay() { + whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) + .thenReturn(true) + + coordinator.onDisplayChanged(displayInfo) + + verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + } + + @Test + fun updateDisplay_notChanged() { + whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) + .thenReturn(false) + + coordinator.onDisplayChanged(displayInfo) + + verify(mockTopologyChangedCallback, never()).invoke(any()) + } + + @Test + fun removeDisplay() { + whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true) + + coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) + + verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + } + + @Test + fun removeDisplay_notChanged() { + whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false) + + coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) + + verify(mockTopologyChangedCallback, never()).invoke(any()) + } + + @Test fun getTopology_copy() { assertThat(coordinator.topology).isEqualTo(mockTopologyCopy) } diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index 96741e0b1e87..469bd66b7e7b 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -21,6 +21,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -889,6 +890,32 @@ public class NotifierTest { "my.package.name", false, null, null); } + @Test + public void getWakelockMonitorTypeForLogging_evaluatesWakelockLevel() { + createNotifier(); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.SCREEN_DIM_WAKE_LOCK), + PowerManager.FULL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.SCREEN_BRIGHT_WAKE_LOCK), PowerManager.FULL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.DRAW_WAKE_LOCK), + PowerManager.DRAW_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.PARTIAL_WAKE_LOCK), + PowerManager.PARTIAL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), + PowerManager.PARTIAL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.DOZE_WAKE_LOCK), -1); + + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity)) + .thenReturn(true); + + createNotifier(); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1); + } + private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() { @Override Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java index 1edbcd527bf4..463254caa845 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java @@ -23,14 +23,10 @@ import static org.mockito.Mockito.spy; import android.compat.testing.PlatformCompatChangeRule; import android.graphics.Rect; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; -import com.android.window.flags.Flags; - import junit.framework.Assert; import org.junit.Rule; @@ -125,8 +121,7 @@ public class AppCompatReachabilityOverridesTest extends WindowTestsBase { } @Test - @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY) - public void testAllowReachabilityForThinLetterboxWithFlagEnabled() { + public void testAllowReachabilityForThinLetterbox_disableForThinLetterboxing() { runTestScenario((robot) -> { robot.activity().createActivityWithComponent(); @@ -142,24 +137,6 @@ public class AppCompatReachabilityOverridesTest extends WindowTestsBase { }); } - @Test - @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY) - public void testAllowReachabilityForThinLetterboxWithFlagDisabled() { - runTestScenario((robot) -> { - robot.activity().createActivityWithComponent(); - - robot.configureIsVerticalThinLetterboxed(/* isThin */ true); - robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true); - robot.configureIsHorizontalThinLetterboxed(/* isThin */ true); - robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true); - - robot.configureIsVerticalThinLetterboxed(/* isThin */ false); - robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true); - robot.configureIsHorizontalThinLetterboxed(/* isThin */ false); - robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true); - }); - } - /** * Runs a test scenario providing a Robot. */ diff --git a/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt b/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt index 281837920548..860d9f680c4c 100644 --- a/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt +++ b/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt @@ -16,10 +16,17 @@ package com.android.test.input +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule + import android.view.KeyCharacterMap import android.view.KeyEvent +import com.android.hardware.input.Flags + import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Rule import org.junit.Test /** @@ -30,26 +37,38 @@ import org.junit.Test * */ class KeyCharacterMapTest { + @get:Rule + val setFlagsRule = SetFlagsRule() + @Test + @EnableFlags(Flags.FLAG_REMOVE_FALLBACK_MODIFIERS) fun testGetFallback() { // Based off of VIRTUAL kcm fallbacks. val keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD) // One modifier fallback. - assertEquals( - keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE, - KeyEvent.META_CTRL_ON).keyCode, - KeyEvent.KEYCODE_LANGUAGE_SWITCH) + val oneModifierFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE, + KeyEvent.META_CTRL_ON) + assertEquals(KeyEvent.KEYCODE_LANGUAGE_SWITCH, oneModifierFallback.keyCode) + assertEquals(0, oneModifierFallback.metaState) // Multiple modifier fallback. - assertEquals( - keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_DEL, - KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON).keyCode, - KeyEvent.KEYCODE_BACK) + val twoModifierFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_DEL, + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON) + assertEquals(KeyEvent.KEYCODE_BACK, twoModifierFallback.keyCode) + assertEquals(0, twoModifierFallback.metaState) // No default button, fallback only. - assertEquals( - keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_BUTTON_A, 0).keyCode, - KeyEvent.KEYCODE_DPAD_CENTER) + val keyOnlyFallback = + keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_BUTTON_A, 0) + assertEquals(KeyEvent.KEYCODE_DPAD_CENTER, keyOnlyFallback.keyCode) + assertEquals(0, keyOnlyFallback.metaState) + + // A key event that is not an exact match for a fallback. Expect a null return. + // E.g. Ctrl + Space -> LanguageSwitch + // Ctrl + Alt + Space -> Ctrl + Alt + Space (No fallback). + val noMatchFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE, + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON) + assertNull(noMatchFallback) } } diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 661df4d0fe33..e24fe07f959b 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -683,8 +683,6 @@ class ChunkPrinter { item->PrettyPrint(printer_); printer_->Print(")"); } - - printer_->Print("\n"); } void PrintQualifiers(uint32_t qualifiers) const { @@ -763,11 +761,13 @@ class ChunkPrinter { bool PrintTableType(const ResTable_type* chunk) { printer_->Print(StringPrintf(" id: 0x%02x", android::util::DeviceToHost32(chunk->id))); - printer_->Print(StringPrintf( - " name: %s", - android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1) - .c_str())); + const auto name = + android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1); + printer_->Print(StringPrintf(" name: %s", name.c_str())); printer_->Print(StringPrintf(" flags: 0x%02x", android::util::DeviceToHost32(chunk->flags))); + printer_->Print(android::util::DeviceToHost32(chunk->flags) & ResTable_type::FLAG_SPARSE + ? " (SPARSE)" + : " (DENSE)"); printer_->Print( StringPrintf(" entryCount: %u", android::util::DeviceToHost32(chunk->entryCount))); printer_->Print( @@ -777,8 +777,7 @@ class ChunkPrinter { config.copyFromDtoH(chunk->config); printer_->Print(StringPrintf(" config: %s\n", config.to_string().c_str())); - const ResourceType* type = ParseResourceType( - android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1)); + const ResourceType* type = ParseResourceType(name); printer_->Indent(); @@ -817,11 +816,8 @@ class ChunkPrinter { for (size_t i = 0; i < map_entry_count; i++) { PrintResValue(&(maps[i].value), config, type); - printer_->Print(StringPrintf( - " name: %s name-id:%d\n", - android::util::GetString(key_pool_, android::util::DeviceToHost32(maps[i].name.ident)) - .c_str(), - android::util::DeviceToHost32(maps[i].name.ident))); + printer_->Print(StringPrintf(" name-id: 0x%08x\n", + android::util::DeviceToHost32(maps[i].name.ident))); } } else { printer_->Print("\n"); @@ -829,6 +825,8 @@ class ChunkPrinter { // Print the value of the entry Res_value value = entry->value(); PrintResValue(&value, config, type); + + printer_->Print("\n"); } printer_->Undent(); |