Merge "Proposal: simplify test with useUnconfinedDispatcher" into main
diff --git a/MEMORY_OWNERS b/MEMORY_OWNERS
index 89ce5140..12aa295 100644
--- a/MEMORY_OWNERS
+++ b/MEMORY_OWNERS
@@ -2,5 +2,4 @@
tjmercier@google.com
kaleshsingh@google.com
jyescas@google.com
-carlosgalo@google.com
jji@google.com
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 852f047..9c6b71b 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -417,6 +417,7 @@
* 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 @@
* 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 @@
* @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 @@
* @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 @@
* @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 aba2345..bfaff94 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1375,6 +1375,9 @@
* @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 @@
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 496d316..1c65b08 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -299,6 +299,24 @@
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 ce8661e..7e0456b 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -340,6 +340,30 @@
}
}
+ /**
+ * 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 321f09b..89a6b02 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -451,6 +451,16 @@
}
}
+ /**
+ * 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
@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 @@
}
}
}
-}
\ 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 a1f31c0..8c0dcfb 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.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 com.android.internal.camera.flags.Flags;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
@@ -46,7 +49,8 @@
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 @@
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 @@
}
@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 @@
}
@Override
- public void stopRepeating() throws CameraAccessException {
- if (mIsPrimary) {
- mSessionImpl.stopRepeating();
+ 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 (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 @@
}
@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 @@
}
@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 a79e084..0b8e9c2 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -65,6 +65,17 @@
}
}
+ 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 @@
}
}
-}
\ 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 e12c463..d394154 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1803,6 +1803,19 @@
}
/**
+ * 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 cdcc92c..365f870 100644
--- a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
@@ -212,7 +212,7 @@
}
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 1e5bed5..de88895 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -539,7 +539,10 @@
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 @@
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/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 313bad5..c4d11cd 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -204,3 +204,10 @@
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 90136ae..ffe8086 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -93,6 +93,10 @@
* 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 @@
*
* <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 fb1bd17..6116d59 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -70,3 +70,11 @@
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 a8d4e2d..48dfdd4 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 @@
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/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index c97d3ec..abd93cf 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -105,6 +105,13 @@
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 76c810b..e91e111 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"
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
/>
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
- 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>
-
- </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 2e0a7af..2d36733 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 f644ade..fbecb8c 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 fc727e1..2d30d8a 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"
- >
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true" />
- <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"
- />
-
- </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 565e28e..45a5d85 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 629a343..bcb1e09 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 73e06f6..8195d38 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/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
index 41d1b5c..eecf199 100644
--- a/libs/WindowManager/Shell/multivalentTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -55,6 +55,7 @@
"truth",
"flag-junit-base",
"flag-junit",
+ "testables",
],
auto_gen_config: true,
}
@@ -77,6 +78,7 @@
"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 0d8f809..3e01256 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.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.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 @@
}
@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 @@
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 3e8a9b6..3188e5b 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 @@
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 c74bf53..9ebb7f5 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 @@
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 @@
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/androidfw/Android.bp b/libs/androidfw/Android.bp
index 1bc15d7..cc4a29b 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -199,6 +199,7 @@
// 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 70d14a1..9704634 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 @@
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;
}
+
+ 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;
}
-static bool keyCompare(uint32_t entry, uint16_t index) {
- return dtohs(ResTable_sparseTypeEntry{entry}.idx) < index;
-}
-
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 @@
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 @@
}
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 fb2fad6..db641b7 100644
--- a/libs/androidfw/include/androidfw/TypeWrappers.h
+++ b/libs/androidfw/include/androidfw/TypeWrappers.h
@@ -27,24 +27,14 @@
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 @@
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 ed30904..d66e058 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 @@
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 @@
}
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;
+ ResValueVector 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);
+ 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 8af4b7e..4fecf4d 100644
--- a/libs/protoutil/Android.bp
+++ b/libs/protoutil/Android.bp
@@ -59,7 +59,6 @@
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 0c2f3ad..023bad2 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -1459,6 +1459,13 @@
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 c02cc80..1b38982 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -167,4 +167,4 @@
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 0000000..f4b26d5
--- /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 0000000..ea38d08
--- /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 0000000..1796e9e
--- /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/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
index 03a2101..218983a 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.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 @@
: 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 @@
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/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java
index 2b8b3b7..c939c77 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 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.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 @@
@RunWith(RobolectricTestRunner.class)
public class SelectorWithWidgetPreferenceTest {
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Application mContext;
private SelectorWithWidgetPreference mPreference;
@@ -128,26 +122,6 @@
}
@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 @@
}
@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 91ac34a..de7c450 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 @@
// 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/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c4d13ba..5ff2d1b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -496,6 +496,13 @@
}
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 {
+ 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 ca2b957..7d27a56 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 @@
// 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 @@
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 @@
// 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 @@
}
private void cancel() {
+ logD("cancel()");
if (mAnimator != null) {
mAnimator.cancel();
}
@@ -311,6 +340,10 @@
}
}
+ 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 5dbedc7..bf3360f 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 @@
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 @@
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/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 2af5ffa..5790c4a 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 @@
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 @@
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 b1a1945..f6c5f58 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 @@
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/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index f821e42..cfbe667 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
@@ -55,16 +55,17 @@
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,
@@ -87,7 +88,7 @@
) {
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)
@@ -100,7 +101,7 @@
}
@Composable
-private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) {
+private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) {
Spacer(
modifier =
modifier
@@ -112,7 +113,7 @@
}
@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 =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
index 85bdf92..cea1e96 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 @@
}
@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 41cc6ee..271cd3a 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 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 @@
@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 @@
windowRootViewVisibilityInteractor,
shadeBackActionInteractor,
qsController,
+ communalBackActionInteractor,
)
}
@@ -306,6 +310,19 @@
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 0000000..c365f1c
--- /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 0bfcd24..8a9c42d 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 @@
}
@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 ee3e241..56e8185 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 @@
// 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 50ac2619..fde9b8c 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 @@
val dndMode = currentModes!!.single()
assertThat(dndMode.isActive).isFalse()
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
}
@Test
@@ -222,7 +222,7 @@
)
// 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 @@
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 @@
)
// 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 @@
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 @@
)
// 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 0000000..18946f9
--- /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 b15352b..173b4e5 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 @@
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 @@
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 @@
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)
+ 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()
+ // 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)
- }
+ // 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)
+ 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()
+ // 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
+ // 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 e9b36b8..9bdc363 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 @@
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 @@
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 @@
}
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 @@
@Test
fun getPickerScreenState_unavailable() =
testScope.runTest {
- setUpState(
- isWalletServiceAvailable = false,
- )
+ setUpState(isWalletServiceAvailable = false)
assertThat(underTest.getPickerScreenState())
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
@@ -195,9 +185,7 @@
@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 @@
@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 @@
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 @@
/*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 46d1ebe..9de0215 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 @@
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/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 0e3b03f..be504cc 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 @@
private val burnInFlow = MutableStateFlow(BurnInModel())
@Before
- @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@DisableSceneContainer
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -219,50 +215,6 @@
}
@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 @@
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_weatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
@@ -344,7 +295,6 @@
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_weatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
@@ -354,7 +304,6 @@
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
@@ -364,7 +313,6 @@
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
@@ -373,7 +321,6 @@
)
@Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@EnableSceneContainer
fun translationAndScale_sceneContainerOn_weatherLargeClock() =
testBurnInViewModelForClocks(
@@ -383,7 +330,6 @@
)
@Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@EnableSceneContainer
fun translationAndScale_sceneContainerOn_weatherSmallClock() =
testBurnInViewModelForClocks(
@@ -393,7 +339,6 @@
)
@Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@EnableSceneContainer
fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
@@ -403,7 +348,6 @@
)
@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 95ffc96..789477e 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 @@
@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/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index de3dc57..d16342b 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.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 @@
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)
@@ -158,7 +159,7 @@
zenModeRepository.addMode(
id = "Bedtime with default icon",
type = AutomaticZenRule.TYPE_BEDTIME,
- active = true
+ active = true,
)
runCurrent()
assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
@@ -259,12 +260,14 @@
val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
const val CUSTOM_DRAWABLE_ID = 12345
+ 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/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 165e943..40f13bb 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 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_noNotifs_empty() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -81,6 +84,7 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_notifMissingStatusBarChipIconView_empty() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -99,6 +103,7 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onePromotedNotif_statusBarIconViewMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -122,6 +127,7 @@
@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 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onePromotedNotif_colorMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -175,6 +182,7 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onlyForPromotedNotifs() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -208,6 +216,7 @@
@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 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_hasShortCriticalText_usesTextInsteadOfTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -272,6 +282,7 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_noTime_isIconOnly() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -294,6 +305,36 @@
}
@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 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_countUpTime_isTimer() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -349,6 +391,7 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_countDownTime_isTimer() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -376,6 +419,7 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_noHeadsUp_showsTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -407,6 +451,7 @@
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_hasHeadsUpByUser_onlyShowsIcon() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -442,6 +487,7 @@
}
@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 0000000..14787e1
--- /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/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index f48fd3c..6bdd86e 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 @@
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/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index ab0f788..ec24c3d 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 fc536bd..6f13d63 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_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 @@
}
// 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 1083136..acfa086 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.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 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 @@
}
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 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;
}
- boolean isConcurrentDisplayActive(Display display) {
- return mIsInConcurrentDisplayState
- && mRearDisplayPhysicalAddress != null
- && mRearDisplayPhysicalAddress.equals(display.getAddress());
+ /**
+ * @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;
+ }
+
+ /**
+ * @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 07bd813..40a86dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -19,13 +19,12 @@
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 @@
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 @@
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 @@
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 232b629..47910f3 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.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.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 @@
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 @@
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 aef5f1f..e6f0245 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 @@
/**
* 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 @@
) : 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 0000000..2ccf96a
--- /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 ea42869..947113d 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 @@
* 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 @@
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 9ae106c..014c0db 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 @@
}
@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 571b37f..b272d65 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.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 @@
mCommunalVisible = communalVisible;
updateLifecycleStateLocked();
+ updateGestureBlockingLocked();
});
}
};
@@ -585,7 +587,8 @@
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 f549e64..d0065c8 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.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 @@
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 74ee052..57f06fb 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.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 @@
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 @@
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 e8d3bfa..1b8baf6 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 @@
): 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 @@
)
} 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 @@
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 480ef5e..e2642a0 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 @@
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 @@
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 @@
} else {
FlashlightState.OffAvailable.toLockScreenState()
},
- TAG
+ TAG,
)
}
override fun onFlashlightError() {
trySendWithFailureLogging(
FlashlightState.OffAvailable.toLockScreenState(),
- TAG
+ TAG,
)
}
@@ -114,7 +113,7 @@
FlashlightState.OffAvailable.toLockScreenState()
}
},
- TAG
+ TAG,
)
}
}
@@ -130,7 +129,7 @@
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 d335a18..06da281 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 @@
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 1cf6183..ade65c3 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.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 @@
/** 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 @@
) : 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 1358634..1c9bc9f 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.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.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 @@
audioManager.ringerModeInternal = newRingerMode
}
}
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
@@ -140,11 +140,11 @@
.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 eafa1ce..cb7702e 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.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 @@
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}\""
+ "Wallet card retrieval error, message: \"${error?.message}\"",
)
- trySendWithFailureLogging(
- null,
- TAG,
- )
+ trySendWithFailureLogging(null, TAG)
}
}
@@ -94,7 +87,7 @@
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 @@
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 @@
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 @@
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 @@
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 ae55825..9c2daf5 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.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 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 @@
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 @@
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/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
index e7803c5..a4a5ba6 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 @@
.apply {
val vibrationDelayMs =
(ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
- .toInt()
+ .toInt()
val vibrationCount = ShakeAnimationCycles.toInt() * 2
repeat(vibrationCount) {
@@ -47,29 +58,13 @@
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 8725cdd..8a2e3dd 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 @@
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.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 @@
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 @@
): 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 @@
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 @@
}
}
}
+
+ 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 @@
com.android.internal.R.color.materialColorOnPrimaryFixed
} else {
com.android.internal.R.color.materialColorOnSurface
- },
+ }
)
)
@@ -221,12 +254,7 @@
.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 @@
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 @@
Size(
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ),
+ )
)
}
@@ -297,7 +331,8 @@
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 @@
slotId = viewModel.slotId,
)
)
- vibratorHelper?.vibrate(
+ vibratorHelper?.playFeedback(
if (viewModel.isActivated) {
KeyguardBottomAreaVibrations.Activated
} else {
KeyguardBottomAreaVibrations.Deactivated
- }
+ },
+ msdlPlayer,
)
}
@@ -328,7 +364,15 @@
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 a2ce4ec..6d270b2 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 @@
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 @@
launch("$TAG#alpha") {
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
- childViews[statusViewId]?.alpha = alpha
childViews[burnInLayerId]?.alpha = alpha
}
}
@@ -253,18 +249,6 @@
}
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 @@
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 090b659..6fb31c0 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.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.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 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 @@
@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 @@
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 @@
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 @@
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 @@
),
)
- 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 @@
.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 @@
},
)
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 @@
// 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 @@
)
}
- 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 @@
}
}
- 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 729759a..5d463f7 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.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 @@
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 1c89723..fb311a5 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.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 @@
(!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 0000000..890628c
--- /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 9066d46..eaba5d5 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.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 @@
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 @@
.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/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index c32bd40..b4dabbe 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.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.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 @@
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 @@
}
}
}
- 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 311cbfb..b2696aea 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 @@
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 @@
override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
controller.showNoteTask(entryPoint = QUICK_AFFORDANCE)
- return OnTriggeredResult.Handled
+ return OnTriggeredResult.Handled(true)
}
}
@@ -194,7 +194,7 @@
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 ef7e7eb..84b995e 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 @@
/**
* 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/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 9b2880b..ee6b0b8 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
@@ -79,7 +79,10 @@
} else {
return ModesTileModel(
isActivated = activeModes.isAnyActive(),
- icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(),
+ icon =
+ context
+ .getDrawable(ModesTile.ICON_RES_ID)!!
+ .asIcon(res = ModesTile.ICON_RES_ID),
iconResId = ModesTile.ICON_RES_ID,
activeModes = activeModes.modeNames,
)
@@ -92,12 +95,18 @@
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/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 66af275..a7dbb47 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 @@
)
}
+ 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 0000000..9f523fc
--- /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 0000000..1663aeb
--- /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 0000000..5712be3
--- /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 0000000..b390f29
--- /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/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 13ad141..a43f8db 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 @@
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 @@
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 0000000..0f21514
--- /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 fe2dabe..74809fd 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 @@
* 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 @@
fun build() =
PromotedNotificationContentModel(
- key = key,
+ identity = Identity(key, style),
skeletonSmallIcon = skeletonSmallIcon,
appName = appName,
subText = subText,
@@ -103,6 +103,8 @@
)
}
+ 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 0000000..adfa6a1
--- /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 0000000..f265e0f
--- /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/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
index 42acd7bc..705845f 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 @@
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 @@
}
}
+ 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 b81c71e..fc8c70f 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 @@
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 @@
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 a382cf9..e08114f 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.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 @@
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 @@
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 @@
if (isFolded) {
foldToAodLatencyTracker.onFolded()
}
- }
+ },
)
/**
@@ -272,6 +258,7 @@
latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
}
}
+
/**
* Called once the Fold -> AOD animation is started.
*
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 32fa160..21dd5bc 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 @@
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 eb19a9c..1ce128c 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 @@
canShowWhileLocked = canShowWhileLocked,
)
} else {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
underTest.onQuickAffordanceTriggered(
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 0000000..57c8fd0
--- /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 548b564..5d20669 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 @@
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 @@
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 0000000..d857157
--- /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 e473107..abbfa93 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 @@
primaryBouncerToLockscreenTransitionViewModel,
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
- aodAlphaViewModel = aodAlphaViewModel,
shadeInteractor = shadeInteractor,
)
}
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 0000000..62cdc87
--- /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 a357275..f31697e8 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 @@
}
}
+ 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 3441d94..9ceca5d 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 @@
* 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 @@
}
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/location/provider/proxy/ProxyGnssAssistanceProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java
new file mode 100644
index 0000000..6cab60c
--- /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/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 271c818..7f88e74 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 @@
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;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 60130d1..30a967c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -91,6 +91,7 @@
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 @@
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.
diff --git a/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt b/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt
index 2818379..860d9f6 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 @@
*
*/
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 661df4d..e24fe07 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -683,8 +683,6 @@
item->PrettyPrint(printer_);
printer_->Print(")");
}
-
- printer_->Print("\n");
}
void PrintQualifiers(uint32_t qualifiers) const {
@@ -763,11 +761,13 @@
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 @@
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 @@
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 @@
// Print the value of the entry
Res_value value = entry->value();
PrintResValue(&value, config, type);
+
+ printer_->Print("\n");
}
printer_->Undent();