Merge "[flexiglass] Adds support for "enhanced PIN privacy"" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 44f3d70..52200bf 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -180,6 +180,18 @@
srcs: ["core/java/android/nfc/*.aconfig"],
}
+cc_aconfig_library {
+ name: "android_nfc_flags_aconfig_c_lib",
+ vendor_available: true,
+ aconfig_declarations: "android.nfc.flags-aconfig",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ "nfc_nci.st21nfc.default",
+ ],
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
java_aconfig_library {
name: "android.nfc.flags-aconfig-java",
aconfig_declarations: "android.nfc.flags-aconfig",
diff --git a/core/api/current.txt b/core/api/current.txt
index 9ce5342..d80b5cb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -28698,14 +28698,17 @@
}
public final class NfcAdapter {
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
method public void disableForegroundDispatch(android.app.Activity);
method public void disableReaderMode(android.app.Activity);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
method public boolean isEnabled();
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
method public boolean isSecureNfcEnabled();
@@ -28813,6 +28816,7 @@
method public boolean removeAidsForService(android.content.ComponentName, String);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
method public boolean supportsAidPrefixRegistration();
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
method public boolean unsetPreferredService(android.app.Activity);
@@ -28832,9 +28836,20 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onDeactivated(int);
method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
method public final void sendResponseApdu(byte[]);
field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index aca6d06..5d06978 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3402,8 +3402,8 @@
new Key<Long>("android.sensor.exposureTime", long.class);
/**
- * <p>Duration from start of frame exposure to
- * start of next frame exposure.</p>
+ * <p>Duration from start of frame readout to
+ * start of next frame readout.</p>
* <p>The maximum frame rate that can be supported by a camera subsystem is
* a function of many factors:</p>
* <ul>
@@ -3464,6 +3464,10 @@
* <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
* OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from
+ * start of frame exposure to start of next frame exposure, which doesn't reflect the
+ * definition from sensor manufacturer. A mobile sensor defines the frame duration as
+ * intervals between sensor readouts.</p>
* <p><b>Units</b>: Nanoseconds</p>
* <p><b>Range of valid values:</b><br>
* See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }.
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1c66f82..0d204f3 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -4103,8 +4103,8 @@
new Key<Long>("android.sensor.exposureTime", long.class);
/**
- * <p>Duration from start of frame exposure to
- * start of next frame exposure.</p>
+ * <p>Duration from start of frame readout to
+ * start of next frame readout.</p>
* <p>The maximum frame rate that can be supported by a camera subsystem is
* a function of many factors:</p>
* <ul>
@@ -4165,6 +4165,10 @@
* <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
* OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from
+ * start of frame exposure to start of next frame exposure, which doesn't reflect the
+ * definition from sensor manufacturer. A mobile sensor defines the frame duration as
+ * intervals between sensor readouts.</p>
* <p><b>Units</b>: Nanoseconds</p>
* <p><b>Range of valid values:</b><br>
* See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }.
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 0c95c2e..f6beec1 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -84,4 +84,6 @@
boolean isReaderOptionSupported();
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
boolean enableReaderOption(boolean enable);
+ boolean isObserveModeSupported();
+ boolean setObserveMode(boolean enabled);
}
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index c7b3b2c..191385a 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -30,6 +30,7 @@
boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
+ boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
boolean unsetOffHostForService(int userHandle, in ComponentName service);
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index c897595..98a980f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -1081,6 +1081,61 @@
}
}
+
+ /**
+ * Returns whether the device supports observer mode or not. When observe
+ * mode is enabled, the NFC hardware will listen for NFC readers, but not
+ * respond to them. When observe mode is disabled, the NFC hardware will
+ * resoond to the reader and proceed with the transaction.
+ * @return true if the mode is supported, false otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean isObserveModeSupported() {
+ try {
+ return sService.isObserveModeSupported();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
+ * Disables observe mode to allow the transaction to proceed. See
+ * {@link #isObserveModeSupported()} for a description of observe mode and
+ * use {@link #disallowTransaction()} to enable observe mode and block
+ * transactions again.
+ *
+ * @return boolean indicating success or failure.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean allowTransaction() {
+ try {
+ return sService.setObserveMode(false);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
+ * Signals that the transaction has completed and observe mode may be
+ * reenabled. See {@link #isObserveModeSupported()} for a description of
+ * observe mode and use {@link #allowTransaction()} to disable observe
+ * mode and allow transactions to proceed.
+ *
+ * @return boolean indicating success or failure.
+ */
+
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean disallowTransaction() {
+ try {
+ return sService.setObserveMode(true);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
/**
* Resumes default polling for the current device state if polling is paused. Calling
* this while polling is not paused is a no-op.
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index d048b59..58b6179 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -328,6 +328,24 @@
return SELECTION_MODE_ASK_IF_CONFLICT;
}
}
+ /**
+ * Sets whether the system should default to observe mode or not when
+ * the service is in the foreground or the default payment service.
+ *
+ * @param service The component name of the service
+ * @param enable Whether the servic should default to observe mode or not
+ * @return whether the change was successful.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) {
+ try {
+ return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(),
+ service, enable);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ }
+ return false;
+ }
/**
* Registers a list of AIDs for a specific category for the
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java
index 55d0e73..7cd2533 100644
--- a/core/java/android/nfc/cardemulation/HostApduService.java
+++ b/core/java/android/nfc/cardemulation/HostApduService.java
@@ -16,11 +16,14 @@
package android.nfc.cardemulation;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -29,6 +32,9 @@
import android.os.RemoteException;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* <p>HostApduService is a convenience {@link Service} class that can be
* extended to emulate an NFC card inside an Android
@@ -230,9 +236,99 @@
/**
* @hide
*/
+ public static final int MSG_POLLING_LOOP = 4;
+
+ /**
+ * @hide
+ */
public static final String KEY_DATA = "data";
/**
+ * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+ * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+
+ /**
+ * POLLING_LOOP_TYPE_A is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop is for NFC-A.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_A = 'A';
+
+ /**
+ * POLLING_LOOP_TYPE_B is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop is for NFC-B.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_B = 'B';
+
+ /**
+ * POLLING_LOOP_TYPE_F is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop is for NFC-F.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_F = 'F';
+
+ /**
+ * POLLING_LOOP_TYPE_ON is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop turns on.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_ON = 'O';
+
+ /**
+ * POLLING_LOOP_TYPE_OFF is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop turns off.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_OFF = 'X';
+
+ /**
+ * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the polling loop frame isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
+
+ /**
+ * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+ * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the frame type isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+
+ /**
+ * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+ * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the frame type isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+
+ /**
+ * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+ * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+ * when the frame type isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+
+ /**
+ * @hide
+ */
+ public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+ "android.nfc.cardemulation.POLLING_FRAMES";
+
+ /**
* Messenger interface to NfcService for sending responses.
* Only accessed on main thread by the message handler.
*
@@ -255,6 +351,7 @@
byte[] apdu = dataBundle.getByteArray(KEY_DATA);
if (apdu != null) {
+ HostApduService has = HostApduService.this;
byte[] responseApdu = processCommandApdu(apdu, null);
if (responseApdu != null) {
if (mNfcService == null) {
@@ -306,6 +403,12 @@
Log.e(TAG, "RemoteException calling into NfcService.");
}
break;
+ case MSG_POLLING_LOOP:
+ ArrayList<Bundle> frames =
+ msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+ Bundle.class);
+ processPollingFrames(frames);
+ break;
default:
super.handleMessage(msg);
}
@@ -366,6 +469,21 @@
}
}
+ /**
+ * This method is called when a polling frame has been received from a
+ * remote device. If the device is in observe mode, the service should
+ * call {@link NfcAdapter#allowTransaction()} once it is ready to proceed
+ * with the transaction. If the device is not in observe mode, the service
+ * can use this polling frame information to determine how to proceed if it
+ * subsequently has {@link #processCommandApdu(byte[], Bundle)} called. The
+ * service must override this method inorder to receive polling frames,
+ * otherwise the base implementation drops the frame.
+ *
+ * @param frame A description of the polling frame.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void processPollingFrames(@NonNull List<Bundle> frame) {
+ }
/**
* <p>This method will be called when a command APDU has been received
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index cd50ace..17e0427 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -20,3 +20,31 @@
description: "Flag for NFC user restriction"
bug: "291187960"
}
+
+flag {
+ name: "nfc_observe_mode"
+ namespace: "nfc"
+ description: "Enable NFC Observe Mode"
+ bug: "294217286"
+}
+
+flag {
+ name: "nfc_read_polling_loop"
+ namespace: "nfc"
+ description: "Enable NFC Polling Loop Notifications"
+ bug: "294217286"
+}
+
+flag {
+ name: "nfc_observe_mode_st_shim"
+ namespace: "nfc"
+ description: "Enable NFC Observe Mode ST shim"
+ bug: "294217286"
+}
+
+flag {
+ name: "nfc_read_polling_loop_st_shim"
+ namespace: "nfc"
+ description: "Enable NFC Polling Loop Notifications ST shim"
+ bug: "294217286"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 0ad6c99..b600b22 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -35,4 +35,11 @@
name: "fullscreen_dim_flag"
description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
bug: "253533308"
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "activity_embedding_interactive_divider_flag"
+ description: "Whether the interactive divider feature is enabled"
+ bug: "293654166"
}
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 3496994..698c5ba 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4303,6 +4303,9 @@
<!-- Whether the device must be screen on before routing data to this service.
The default is true.-->
<attr name="requireDeviceScreenOn" format="boolean"/>
+ <!-- Whether the device should default to observe mode when this service is
+ default or in the foreground. -->
+ <attr name="defaultToObserveMode" format="boolean"/>
</declare-styleable>
<!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that
@@ -4327,6 +4330,9 @@
<!-- Whether the device must be screen on before routing data to this service.
The default is false.-->
<attr name="requireDeviceScreenOn"/>
+ <!-- Whether the device should default to observe mode when this service is
+ default or in the foreground. -->
+ <attr name="defaultToObserveMode"/>
</declare-styleable>
<!-- Specify one or more <code>aid-group</code> elements inside a
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 3e3c77b..03c38cc 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -25,9 +25,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.annotation.EnforcePermission;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.test.FakePermissionEnforcer;
import androidx.test.filters.SmallTest;
@@ -57,7 +59,8 @@
@Before
public void setUp() {
- mService = new TestDeviceStateManagerService();
+ FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer();
+ mService = new TestDeviceStateManagerService(permissionEnforcer);
mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService);
assertFalse(mService.mCallbacks.isEmpty());
}
@@ -261,6 +264,10 @@
private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
+ TestDeviceStateManagerService(FakePermissionEnforcer enforcer) {
+ super(enforcer);
+ }
+
private DeviceStateInfo getInfo() {
final int mergedBaseState = mBaseStateRequest == null
? mBaseState : mBaseStateRequest.state;
@@ -380,7 +387,10 @@
// No-op in the test since DeviceStateManagerGlobal just calls into the system server with
// no business logic around it.
@Override
- public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {}
+ @EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {
+ onStateRequestOverlayDismissed_enforcePermission();
+ }
public void setSupportedStates(int[] states) {
mSupportedStates = states;
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index a1b05c1..a7d6423 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -597,7 +597,13 @@
SkIRect clipBounds;
if (enableClip) {
uirenderer::Rect initialClipBounds;
- props.getClippingRectForFlags(props.getClippingFlags(), &initialClipBounds);
+ const auto clipFlags = props.getClippingFlags();
+ if (clipFlags) {
+ props.getClippingRectForFlags(clipFlags, &initialClipBounds);
+ } else {
+ // Works for RenderNode::damageSelf()
+ initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+ }
clipBounds =
info.damageAccumulator
->computeClipAndTransform(initialClipBounds.toSkRect(), &transform)
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
new file mode 100644
index 0000000..92cc923
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Interface to the TV AD service.
+ * @hide
+ */
+interface ITvAdManager {
+ void startAdService(in IBinder sessionToken, int userId);
+}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
new file mode 100644
index 0000000..b834f1b9
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Sub-interface of ITvAdService which is created per session and has its own context.
+ * @hide
+ */
+oneway interface ITvAdSession {
+ void startAdService();
+}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
new file mode 100644
index 0000000..aa5a290
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Central system API to the overall client-side TV AD architecture, which arbitrates interaction
+ * between applications and AD services.
+ * @hide
+ */
+public class TvAdManager {
+ private static final String TAG = "TvAdManager";
+
+ private final ITvAdManager mService;
+ private final int mUserId;
+
+ public TvAdManager(ITvAdManager service, int userId) {
+ mService = service;
+ mUserId = userId;
+ }
+
+ /**
+ * The Session provides the per-session functionality of AD service.
+ */
+ public static final class Session {
+ private final IBinder mToken;
+ private final ITvAdManager mService;
+ private final int mUserId;
+
+ private Session(IBinder token, ITvAdManager service, int userId) {
+ mToken = token;
+ mService = service;
+ mUserId = userId;
+ }
+
+ void startAdService() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.startAdService(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
new file mode 100644
index 0000000..61101f0
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.app.Service;
+import android.view.KeyEvent;
+
+/**
+ * The TvAdService class represents a TV client-side advertisement service.
+ * @hide
+ */
+public abstract class TvAdService extends Service {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvAdService";
+
+ /**
+ * Base class for derived classes to implement to provide a TV AD session.
+ */
+ public abstract static class Session implements KeyEvent.Callback {
+ /**
+ * Starts TvAdService session.
+ */
+ public void onStartAdService() {
+ }
+
+ void startAdService() {
+ onStartAdService();
+ }
+ }
+
+ /**
+ * Implements the internal ITvAdService interface.
+ */
+ public static class ITvAdSessionWrapper extends ITvAdSession.Stub {
+ private final Session mSessionImpl;
+
+ public ITvAdSessionWrapper(Session mSessionImpl) {
+ this.mSessionImpl = mSessionImpl;
+ }
+
+ @Override
+ public void startAdService() {
+ mSessionImpl.startAdService();
+ }
+ }
+}
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
new file mode 100644
index 0000000..1a3771a
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.ViewGroup;
+
+/**
+ * Displays contents of TV AD services.
+ * @hide
+ */
+public class TvAdView extends ViewGroup {
+ private static final String TAG = "TvAdView";
+ private static final boolean DEBUG = false;
+
+ // TODO: create session
+ private TvAdManager.Session mSession;
+
+ public TvAdView(Context context) {
+ super(context, /* attrs = */null, /* defStyleAttr = */0);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+ }
+ }
+
+ /**
+ * Starts the AD service.
+ */
+ public void startAdService() {
+ if (DEBUG) {
+ Log.d(TAG, "start");
+ }
+ if (mSession != null) {
+ mSession.startAdService();
+ }
+ }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 0e9f8b1..80fd516 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -294,8 +294,6 @@
"tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
"tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
"tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt",
- "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
- "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
"tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt",
"tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt",
// Keyguard helper
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index 6082fb9..8ad32b4 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -25,7 +25,11 @@
UDFPS_ULTRASONIC,
UDFPS_OPTICAL,
POWER_BUTTON,
- HOME_BUTTON
+ HOME_BUTTON;
+
+ fun isUdfps(): Boolean {
+ return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC)
+ }
}
/** Convert [this] to corresponding [FingerprintSensorType] */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index c3f3529..298811b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -29,6 +29,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
@@ -46,6 +47,7 @@
* Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless
* of the authentication method used.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
class DeviceEntryInteractor
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
new file mode 100644
index 0000000..72b9da6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Encapsulates business logic for device entry under-display fingerprint state changes. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryUdfpsInteractor
+@Inject
+constructor(
+ // TODO (b/309655554): create & use interactors for these repositories
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+ fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+ biometricSettingsRepository: BiometricSettingsRepository,
+) {
+ /** Whether the device supports an under display fingerprint sensor. */
+ val isUdfpsSupported: Flow<Boolean> =
+ fingerprintPropertyRepository.sensorType.map { it.isUdfps() }
+
+ /** Whether the under-display fingerprint sensor is enrolled and enabled for device entry. */
+ val isUdfpsEnrolledAndEnabled: Flow<Boolean> =
+ combine(isUdfpsSupported, biometricSettingsRepository.isFingerprintEnrolledAndEnabled) {
+ udfps,
+ fpEnrolledAndEnabled ->
+ udfps && fpEnrolledAndEnabled
+ }
+ /** Whether the under display fingerprint sensor is currently running. */
+ val isListeningForUdfps =
+ isUdfpsSupported.flatMapLatest { isUdfps ->
+ if (isUdfps) {
+ fingerprintAuthRepository.isRunning
+ } else {
+ flowOf(false)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 8b93b17..331d892 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -55,6 +55,7 @@
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -94,6 +95,7 @@
KeyguardStatusViewComponent.class,
KeyguardUserSwitcherComponent.class},
includes = {
+ DeviceEntryIconTransitionModule.class,
FalsingModule.class,
KeyguardDataQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index 8bf2bc3..cc1cf91 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -50,14 +50,18 @@
private val configurationRepository: ConfigurationRepository,
private val keyguardInteractor: KeyguardInteractor,
) {
- val udfpsBurnInXOffset: StateFlow<Int> =
+ val deviceEntryIconXOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
- val udfpsBurnInYOffset: StateFlow<Int> =
+ val deviceEntryIconYOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
- val udfpsBurnInProgress: StateFlow<Float> =
+ val udfpsProgress: StateFlow<Float> =
keyguardInteractor.dozeTimeTick
.mapLatest { burnInHelperWrapper.burnInProgressOffset() }
- .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset())
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ burnInHelperWrapper.burnInProgressOffset()
+ )
val keyguardBurnIn: Flow<BurnInModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index a331a66..8584401 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -113,7 +113,9 @@
}
companion object {
- val TO_LOCKSCREEN_DURATION = 500.milliseconds
private val DEFAULT_DURATION = 500.milliseconds
+ val TO_LOCKSCREEN_DURATION = 500.milliseconds
+ val TO_GONE_DURATION = DEFAULT_DURATION
+ val TO_OCCLUDED_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index e9719e7..eca7088 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -26,11 +26,11 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
@SysUISingleton
class FromDozingTransitionInteractor
@@ -97,5 +97,6 @@
companion object {
private val DEFAULT_DURATION = 500.milliseconds
+ val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index eace0c7..bd73d60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -138,6 +138,6 @@
companion object {
private val DEFAULT_DURATION = 500.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
- val TO_AOD_DURATION = 1100.milliseconds
+ val TO_AOD_DURATION = 1300.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index ea40ba0..152d217 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -391,5 +391,7 @@
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
val TO_AOD_DURATION = 500.milliseconds
+ val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
+ val TO_GONE_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index dec38b5..6a8555c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -142,9 +142,7 @@
::toTriple
)
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (
- lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep
- ) {
+ if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) {
startTransitionTo(
if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
)
@@ -187,5 +185,6 @@
companion object {
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 933.milliseconds
+ val TO_AOD_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 24b6661..5f246e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -255,5 +255,7 @@
private val DEFAULT_DURATION = 300.milliseconds
val TO_GONE_DURATION = 500.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
+ val TO_AOD_DURATION = DEFAULT_DURATION
+ val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
index c0308e6..f5cd767 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -51,14 +51,14 @@
val scaleForResolution = configRepo.scaleForResolution
/** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
- val burnInOffsets: Flow<BurnInOffsets> =
+ val burnInOffsets: Flow<Offsets> =
combine(
keyguardInteractor.dozeAmount,
- burnInInteractor.udfpsBurnInXOffset,
- burnInInteractor.udfpsBurnInYOffset,
- burnInInteractor.udfpsBurnInProgress
+ burnInInteractor.deviceEntryIconXOffset,
+ burnInInteractor.deviceEntryIconYOffset,
+ burnInInteractor.udfpsProgress
) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
- BurnInOffsets(
+ Offsets(
intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX),
intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY),
floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress),
@@ -86,8 +86,8 @@
.onStart { emit(0f) }
}
-data class BurnInOffsets(
- val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount
- val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount
- val burnInProgress: Float, // current progress based on the aodTransitionAmount
+data class Offsets(
+ val x: Int, // current x burn in offset based on the aodTransitionAmount
+ val y: Int, // current y burn in offset based on the aodTransitionAmount
+ val progress: Float, // current progress based on the aodTransitionAmount
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 9d7477c..d5ad7ab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -105,4 +105,9 @@
}
.filterNotNull()
}
+
+ /** Immediately (after 1ms) emits the given value for every step of the KeyguardTransition. */
+ fun immediatelyTransitionTo(value: Float): Flow<Float> {
+ return createFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index e82ea7f..a8b28bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -24,6 +24,8 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.common.ui.view.LongPressHandlingView
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
@@ -34,19 +36,24 @@
object DeviceEntryIconViewBinder {
/**
- * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its
- * background.
+ * Updates UI for:
+ * - device entry containing view (parent view for the below views)
+ * - long-press handling view (transparent, no UI)
+ * - foreground icon view (lock/unlock/fingerprint)
+ * - background view (optional)
*/
@SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: DeviceEntryIconView,
viewModel: DeviceEntryIconViewModel,
+ fgViewModel: DeviceEntryForegroundViewModel,
+ bgViewModel: DeviceEntryBackgroundViewModel,
falsingManager: FalsingManager,
) {
- val iconView = view.iconView
- val bgView = view.bgView
val longPressHandlingView = view.longPressHandlingView
+ val fgIconView = view.iconView
+ val bgView = view.bgView
longPressHandlingView.listener =
object : LongPressHandlingView.Listener {
override fun onLongPressDetected(view: View, x: Int, y: Int) {
@@ -56,37 +63,12 @@
viewModel.onLongPress()
}
}
+
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.iconViewModel.collect { iconViewModel ->
- iconView.setImageState(
- view.getIconState(iconViewModel.type, iconViewModel.useAodVariant),
- /* merge */ false
- )
- iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint)
- iconView.alpha = iconViewModel.alpha
- iconView.setPadding(
- iconViewModel.padding,
- iconViewModel.padding,
- iconViewModel.padding,
- iconViewModel.padding,
- )
- }
- }
- launch {
- viewModel.backgroundViewModel.collect { bgViewModel ->
- bgView.alpha = bgViewModel.alpha
- bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
- }
- }
- launch {
- viewModel.burnInViewModel.collect { burnInViewModel ->
- view.translationX = burnInViewModel.x.toFloat()
- view.translationY = burnInViewModel.y.toFloat()
- view.aodFpDrawable.progress = burnInViewModel.progress
- }
- }
+ // Repeat on CREATED so that the view will always observe the entire
+ // GONE => AOD transition (even though the view may not be visible until the middle
+ // of the transition.
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.isLongPressEnabled.collect { isEnabled ->
longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
@@ -97,6 +79,55 @@
view.accessibilityHintType = hint
}
}
+ launch {
+ viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
+ if (useBackgroundProtection) {
+ bgView.visibility = View.VISIBLE
+ } else {
+ bgView.visibility = View.GONE
+ }
+ }
+ }
+ launch {
+ viewModel.burnInOffsets.collect { burnInOffsets ->
+ view.translationX = burnInOffsets.x.toFloat()
+ view.translationY = burnInOffsets.y.toFloat()
+ view.aodFpDrawable.progress = burnInOffsets.progress
+ }
+ }
+
+ launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } }
+ }
+ }
+
+ fgIconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ fgViewModel.viewModel.collect { viewModel ->
+ fgIconView.setImageState(
+ view.getIconState(viewModel.type, viewModel.useAodVariant),
+ /* merge */ false
+ )
+ fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint)
+ fgIconView.setPadding(
+ viewModel.padding,
+ viewModel.padding,
+ viewModel.padding,
+ viewModel.padding,
+ )
+ }
+ }
+ }
+ }
+
+ bgView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ bgViewModel.viewModel.collect { bgViewModel ->
+ bgView.alpha = bgViewModel.alpha
+ bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
index 9872d97..52d87d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
@@ -42,9 +42,9 @@
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.burnInOffsets.collect { burnInOffsets ->
- view.progress = burnInOffsets.burnInProgress
- view.translationX = burnInOffsets.burnInXOffset.toFloat()
- view.translationY = burnInOffsets.burnInYOffset.toFloat()
+ view.progress = burnInOffsets.progress
+ view.translationX = burnInOffsets.x.toFloat()
+ view.translationY = burnInOffsets.y.toFloat()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
index bab04f2..d4621e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -59,8 +59,8 @@
launch {
viewModel.burnInOffsets.collect { burnInOffsets ->
- view.translationX = burnInOffsets.burnInXOffset.toFloat()
- view.translationY = burnInOffsets.burnInYOffset.toFloat()
+ view.translationX = burnInOffsets.x.toFloat()
+ view.translationY = burnInOffsets.y.toFloat()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt
new file mode 100644
index 0000000..b58a80f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.transitions
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Each DeviceEntryIconTransition is responsible for updating the given parameters for the current
+ * keyguard transition.
+ * *
+ * MUST list implementing classes in dagger module [DeviceEntryIconTransitionModule].
+ */
+interface DeviceEntryIconTransition {
+ val deviceEntryParentViewAlpha: Flow<Float>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
new file mode 100644
index 0000000..9d557bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.ui.transitions
+
+import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+@Module
+abstract class DeviceEntryIconTransitionModule {
+ @Binds
+ @IntoSet
+ abstract fun aodToLockscreen(
+ impl: AodToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun dozingToLockscreen(
+ impl: DozingToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun dreamingToLockscreen(
+ impl: DreamingToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenToAod(
+ impl: LockscreenToAodTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenToDreaming(
+ impl: LockscreenToDreamingTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenToOccluded(
+ impl: LockscreenToOccludedTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenToPrimaryBouncer(
+ impl: LockscreenToPrimaryBouncerTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenToGone(
+ impl: LockscreenToGoneTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun goneToAod(impl: GoneToAodTransitionViewModel): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun occludedToAod(impl: OccludedToAodTransitionViewModel): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun occludedToLockscreen(
+ impl: OccludedToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun primaryBouncerToAod(
+ impl: PrimaryBouncerToAodTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun primaryBouncerToLockscreen(
+ impl: PrimaryBouncerToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index c9e3954..af1d0df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -40,7 +40,10 @@
attrs: AttributeSet?,
defStyleAttrs: Int = 0,
) : FrameLayout(context, attrs, defStyleAttrs) {
- val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs)
+ val longPressHandlingView: LongPressHandlingView =
+ LongPressHandlingView(context, attrs) {
+ context.resources.getInteger(R.integer.config_lockIconLongPress).toLong()
+ }
val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg }
val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg }
val aodFpDrawable: LottieDrawable = LottieDrawable()
@@ -105,7 +108,7 @@
// FINGERPRINT
animatedIconDrawable.addState(
getIconState(IconType.FINGERPRINT, false),
- context.getDrawable(R.drawable.ic_kg_fingerprint)!!,
+ context.getDrawable(R.drawable.ic_fingerprint)!!,
R.id.locked_fp,
)
@@ -220,7 +223,7 @@
val lp = longPressHandlingView.layoutParams as LayoutParams
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
- longPressHandlingView.setLayoutParams(lp)
+ longPressHandlingView.layoutParams = lp
}
private fun addIconImageView() {
@@ -231,7 +234,7 @@
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.gravity = Gravity.CENTER
- iconView.setLayoutParams(lp)
+ iconView.layoutParams = lp
}
private fun addBgImageView() {
@@ -240,7 +243,7 @@
val lp = bgView.layoutParams as LayoutParams
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
- bgView.setLayoutParams(lp)
+ bgView.layoutParams = lp
}
fun getIconState(icon: IconType, aod: Boolean): IntArray {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
index ace970a..13ea8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
@@ -36,6 +36,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
@@ -56,12 +58,15 @@
private val featureFlags: FeatureFlags,
private val lockIconViewController: Lazy<LockIconViewController>,
private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+ private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
+ private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
) : KeyguardSection() {
private val deviceEntryIconViewId = R.id.device_entry_icon_view
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!keyguardBottomAreaRefactor() &&
+ if (
+ !keyguardBottomAreaRefactor() &&
!featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)
) {
return
@@ -87,6 +92,8 @@
DeviceEntryIconViewBinder.bind(
it,
deviceEntryIconViewModel.get(),
+ deviceEntryForegroundViewModel.get(),
+ deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
new file mode 100644
index 0000000..4d2af0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Breaks down AOD->GONE transition into discrete steps for corresponding views to consume. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AodToGoneTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+) : DeviceEntryIconTransition {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromAodTransitionInteractor.TO_GONE_DURATION,
+ transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.GONE),
+ )
+
+ override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index 024707a..14de01b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -17,22 +17,29 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
/**
* Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
class AodToLockscreenTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
-) {
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) : DeviceEntryIconTransition {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
@@ -47,4 +54,21 @@
onStart = { 1f },
onStep = { 1f },
)
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps ->
+ if (isUdfps) {
+ // fade in
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { it },
+ onFinish = { 1f },
+ )
+ } else {
+ // background view isn't visible, so return an empty flow
+ emptyFlow()
+ }
+ }
+
+ override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
new file mode 100644
index 0000000..06661d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+
+/** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */
+@SysUISingleton
+class AodToOccludedTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
+ transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED),
+ )
+
+ override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
new file mode 100644
index 0000000..3e8bbb3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import com.android.settingslib.Utils
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+
+/** Models the UI state for the device entry icon background view. */
+@ExperimentalCoroutinesApi
+class DeviceEntryBackgroundViewModel
+@Inject
+constructor(
+ val context: Context,
+ configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor
+ lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
+ aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+ primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
+ occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
+ occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+) {
+ private val color: Flow<Int> =
+ configurationRepository.onAnyConfigurationChange
+ .map {
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface)
+ }
+ .onStart {
+ emit(
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.colorSurface
+ )
+ )
+ }
+ private val alpha: Flow<Float> =
+ setOf(
+ lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ goneToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ )
+ .merge()
+
+ val viewModel: Flow<BackgroundViewModel> =
+ combine(color, alpha) { color, alpha ->
+ BackgroundViewModel(
+ alpha = alpha,
+ tint = color,
+ )
+ }
+
+ data class BackgroundViewModel(
+ val alpha: Float,
+ val tint: Int,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
new file mode 100644
index 0000000..99529a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import com.android.settingslib.Utils
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Models the UI state for the device entry icon foreground view (displayed icon). */
+@ExperimentalCoroutinesApi
+class DeviceEntryForegroundViewModel
+@Inject
+constructor(
+ val context: Context,
+ configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+ deviceEntryIconViewModel: DeviceEntryIconViewModel,
+) {
+ private val isShowingAod: Flow<Boolean> =
+ transitionInteractor.startedKeyguardState.map { keyguardState ->
+ keyguardState == KeyguardState.AOD
+ }
+ private val color: Flow<Int> =
+ configurationRepository.onAnyConfigurationChange
+ .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
+ .onStart {
+ emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
+ }
+ private val useAodIconVariant: Flow<Boolean> =
+ combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) {
+ isTransitionToAod,
+ isUdfps ->
+ isTransitionToAod && isUdfps
+ }
+ .distinctUntilChanged()
+ private val padding: Flow<Int> =
+ configurationRepository.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+
+ val viewModel: Flow<ForegroundIconViewModel> =
+ combine(
+ deviceEntryIconViewModel.iconType,
+ useAodIconVariant,
+ color,
+ padding,
+ ) { iconType, useAodVariant, color, padding ->
+ ForegroundIconViewModel(
+ type = iconType,
+ useAodVariant = useAodVariant,
+ tint = color,
+ padding = padding,
+ )
+ }
+
+ data class ForegroundIconViewModel(
+ val type: DeviceEntryIconView.IconType,
+ val useAodVariant: Boolean,
+ val tint: Int,
+ val padding: Int,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 842dde3..5b5a103 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -12,57 +12,202 @@
* 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 android.graphics.Color
+import android.animation.FloatEvaluator
+import android.animation.IntEvaluator
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+/** Models the UI state for the containing device entry icon & long-press handling view. */
@ExperimentalCoroutinesApi
-class DeviceEntryIconViewModel @Inject constructor() {
- // TODO: b/305234447 update these states from the data layer
- val iconViewModel: Flow<IconViewModel> =
- flowOf(
- IconViewModel(
- type = DeviceEntryIconView.IconType.LOCK,
- useAodVariant = false,
- tint = Color.WHITE,
- alpha = 1f,
- padding = 48,
+class DeviceEntryIconViewModel
+@Inject
+constructor(
+ transitions: Set<@JvmSuppressWildcards DeviceEntryIconTransition>,
+ burnInInteractor: BurnInInteractor,
+ shadeInteractor: ShadeInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+ val keyguardInteractor: KeyguardInteractor,
+ val viewModel: AodToLockscreenTransitionViewModel,
+ val shadeDependentFlows: ShadeDependentFlows,
+ private val sceneContainerFlags: SceneContainerFlags,
+ private val keyguardViewController: Lazy<KeyguardViewController>,
+ private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
+ udfpsInteractor: DeviceEntryUdfpsInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+) {
+ private val intEvaluator = IntEvaluator()
+ private val floatEvaluator = FloatEvaluator()
+ private val toAodFromState: Flow<KeyguardState> =
+ transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from }
+ private val showingAlternateBouncer: Flow<Boolean> =
+ transitionInteractor.startedKeyguardState.map { keyguardState ->
+ keyguardState == KeyguardState.ALTERNATE_BOUNCER
+ }
+ private val qsProgress: Flow<Float> = shadeInteractor.qsExpansion.onStart { emit(0f) }
+ private val shadeExpansion: Flow<Float> = shadeInteractor.shadeExpansion.onStart { emit(0f) }
+ private val transitionAlpha: Flow<Float> =
+ transitions.map { it.deviceEntryParentViewAlpha }.merge()
+ private val alphaMultiplierFromShadeExpansion: Flow<Float> =
+ combine(
+ showingAlternateBouncer,
+ shadeExpansion,
+ qsProgress,
+ ) { showingAltBouncer, shadeExpansion, qsProgress ->
+ val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
+ if (showingAltBouncer) {
+ 1f
+ } else {
+ (1f - shadeExpansion) * (1f - interpolatedQsProgress)
+ }
+ }
+ // Burn-in offsets in AOD
+ private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> =
+ combine(
+ burnInInteractor.deviceEntryIconXOffset,
+ burnInInteractor.deviceEntryIconYOffset,
+ burnInInteractor.udfpsProgress
+ ) { fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
+ BurnInOffsets(
+ fullyDozingBurnInX,
+ fullyDozingBurnInY,
+ fullyDozingBurnInProgress,
)
- )
- val backgroundViewModel: Flow<BackgroundViewModel> =
- flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY))
- val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f))
- val isLongPressEnabled: Flow<Boolean> = flowOf(true)
+ }
+ // Burn-in offsets that animate based on the transition amount to AOD
+ private val animatedBurnInOffsets: Flow<BurnInOffsets> =
+ combine(
+ nonAnimatedBurnInOffsets,
+ transitionInteractor.transitionStepsToState(KeyguardState.AOD)
+ ) { burnInOffsets, transitionStepsToAod ->
+ val dozeAmount = transitionStepsToAod.value
+ BurnInOffsets(
+ intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x),
+ intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y),
+ floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress)
+ )
+ }
+
+ val deviceEntryViewAlpha: Flow<Float> =
+ combine(
+ transitionAlpha,
+ alphaMultiplierFromShadeExpansion,
+ ) { alpha, alphaMultiplier ->
+ alpha * alphaMultiplier
+ }
+ val useBackgroundProtection: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+ val burnInOffsets: Flow<BurnInOffsets> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
+ if (udfpsEnrolled) {
+ toAodFromState.flatMapLatest { fromState ->
+ when (fromState) {
+ KeyguardState.AOD,
+ KeyguardState.GONE,
+ KeyguardState.OCCLUDED,
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ KeyguardState.OFF,
+ KeyguardState.DOZING,
+ KeyguardState.DREAMING,
+ KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets
+ KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
+ KeyguardState.LOCKSCREEN ->
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets,
+ flowWhenShadeIsNotExpanded = animatedBurnInOffsets,
+ )
+ }
+ }
+ } else {
+ // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need
+ // to use burn in offsets at all
+ flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f))
+ }
+ }
+ val iconType: Flow<DeviceEntryIconView.IconType> =
+ combine(
+ udfpsInteractor.isListeningForUdfps,
+ deviceEntryInteractor.isUnlocked,
+ ) { isListeningForUdfps, isUnlocked ->
+ if (isUnlocked) {
+ DeviceEntryIconView.IconType.UNLOCK
+ } else {
+ if (isListeningForUdfps) {
+ DeviceEntryIconView.IconType.FINGERPRINT
+ } else {
+ DeviceEntryIconView.IconType.LOCK
+ }
+ }
+ }
+ val isLongPressEnabled: Flow<Boolean> =
+ combine(
+ iconType,
+ deviceEntryUdfpsInteractor.isUdfpsSupported,
+ ) { deviceEntryStatus, isUdfps ->
+ when (deviceEntryStatus) {
+ DeviceEntryIconView.IconType.LOCK -> isUdfps
+ DeviceEntryIconView.IconType.UNLOCK -> true
+ DeviceEntryIconView.IconType.FINGERPRINT -> false
+ }
+ }
val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
- flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
+ combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled ->
+ if (longPressEnabled) {
+ deviceEntryStatus.toAccessibilityHintType()
+ } else {
+ DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+ }
fun onLongPress() {
- // TODO() vibrate & perform action based on current lock/unlock state
+ deviceEntryHapticsInteractor.vibrateSuccess()
+
+ // TODO (b/309804148): play auth ripple via an interactor
+
+ if (sceneContainerFlags.isEnabled()) {
+ deviceEntryInteractor.attemptDeviceEntry()
+ } else {
+ keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
+ }
}
- data class BurnInViewModel(
- val x: Int, // current x burn in offset based on the aodTransitionAmount
- val y: Int, // current y burn in offset based on the aodTransitionAmount
- val progress: Float, // current progress based on the aodTransitionAmount
- )
- class IconViewModel(
- val type: DeviceEntryIconView.IconType,
- val useAodVariant: Boolean,
- val tint: Int,
- val alpha: Float,
- val padding: Int,
- )
-
- class BackgroundViewModel(
- val alpha: Float,
- val tint: Int,
- )
+ private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
+ DeviceEntryIconView.AccessibilityHintType {
+ return when (this) {
+ DeviceEntryIconView.IconType.LOCK ->
+ DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE
+ DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
+ DeviceEntryIconView.IconType.FINGERPRINT ->
+ DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+ }
}
+
+data class BurnInOffsets(
+ val x: Int, // current x burn in offset based on the aodTransitionAmount
+ val y: Int, // current y burn in offset based on the aodTransitionAmount
+ val progress: Float, // current progress based on the aodTransitionAmount
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..27fb8a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down DOZING->LOCKSCREEN transition into discrete steps for corresponding views to consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DozingToLockscreenTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation: KeyguardTransitionAnimationFlow =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.dozingToLockscreenTransition,
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index e24d326..a3b8b85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -18,27 +18,34 @@
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
* consume.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
class DreamingToLockscreenTransitionViewModel
@Inject
constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
- private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
-) {
+ private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
+ private val deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) : DeviceEntryIconTransition {
fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition()
private val transitionAnimation =
@@ -88,4 +95,15 @@
duration = 250.milliseconds,
onStep = { it },
)
+
+ val deviceEntryBackgroundViewAlpha =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps ->
+ if (isUdfps) {
+ // immediately show; will fade in with deviceEntryParentViewAlpha
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+ override val deviceEntryParentViewAlpha = lockscreenAlpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index 601dbcc..62b2281 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -18,20 +18,27 @@
import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */
+@ExperimentalCoroutinesApi
@SysUISingleton
class GoneToAodTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
-) {
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) : DeviceEntryIconTransition {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
@@ -60,4 +67,21 @@
onStart = { 0f },
onStep = { it },
)
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
+ if (udfpsEnrolled) {
+ // fade in at the end of the transition to give time for FP to start running
+ // and avoid a flicker of the unlocked icon
+ transitionAnimation.createFlow(
+ startTime = 1100.milliseconds,
+ duration = 200.milliseconds,
+ onStep = { it },
+ onFinish = { 1f },
+ )
+ } else {
+ emptyFlow()
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
new file mode 100644
index 0000000..2bf12e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Breaks down LOCKSCREEN->AOD transition into discrete steps for corresponding views to consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LockscreenToAodTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ shadeDependentFlows: ShadeDependentFlows,
+) : DeviceEntryIconTransition {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromLockscreenTransitionInteractor.TO_AOD_DURATION,
+ transitionFlow = interactor.lockscreenToAodTransition,
+ )
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.createFlow(
+ duration = 300.milliseconds,
+ onStep = { 1 - it },
+ onFinish = { 0f },
+ ),
+ )
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
+ isUdfpsEnrolledAndEnabled ->
+ if (isUdfpsEnrolledAndEnabled) {
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded = // fade in
+ transitionAnimation.createFlow(
+ duration = 300.milliseconds,
+ onStep = { it },
+ onFinish = { 1f },
+ ),
+ flowWhenShadeIsNotExpanded = transitionAnimation.immediatelyTransitionTo(1f),
+ )
+ } else {
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
+ flowWhenShadeIsNotExpanded = // fade out
+ transitionAnimation.createFlow(
+ duration = 200.milliseconds,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ ),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index a3ae67d..5229613 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -34,7 +35,8 @@
@Inject
constructor(
interactor: KeyguardTransitionInteractor,
-) {
+ shadeDependentFlows: ShadeDependentFlows,
+) : DeviceEntryIconTransition {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_DREAMING_DURATION,
@@ -60,6 +62,12 @@
onStep = { 1f - it },
)
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded = lockscreenAlpha,
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
+ )
+
companion object {
@JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
new file mode 100644
index 0000000..59e5aa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down LOCKSCREEN->GONE transition into discrete steps for corresponding views to consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LockscreenToGoneTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+) : DeviceEntryIconTransition {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
+ transitionFlow = interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index d3ea89c..d49bc49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -33,8 +34,9 @@
class LockscreenToOccludedTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
-) {
+ interactor: KeyguardTransitionInteractor,
+ shadeDependentFlows: ShadeDependentFlows,
+) : DeviceEntryIconTransition {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_OCCLUDED_DURATION,
@@ -59,4 +61,10 @@
interpolator = EMPHASIZED_ACCELERATE,
)
}
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded = lockscreenAlpha,
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
new file mode 100644
index 0000000..f04b67a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LockscreenToPrimaryBouncerTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+ shadeDependentFlows: ShadeDependentFlows,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ transitionFlow =
+ interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER),
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ onFinish = { 0f }
+ ),
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f)
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
new file mode 100644
index 0000000..f7cff9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/** Breaks down OCCLUDED->AOD transition into discrete steps for corresponding views to consume. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludedToAodTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromOccludedTransitionInteractor.TO_AOD_DURATION,
+ transitionFlow = interactor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD),
+ )
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
+ ->
+ if (udfpsEnrolledAndEnabled) {
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 6845c55..0bdc85d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -18,23 +18,30 @@
import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
/**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
* consume.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
class OccludedToLockscreenTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
-) {
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor
+) : DeviceEntryIconTransition {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_LOCKSCREEN_DURATION,
@@ -58,4 +65,16 @@
duration = 250.milliseconds,
onStep = { it },
)
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
+ isUdfpsEnrolledAndEnabled ->
+ if (isUdfpsEnrolledAndEnabled) {
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+
+ override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
new file mode 100644
index 0000000..05a6d58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Breaks down PRIMARY BOUNCER->AOD transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class PrimaryBouncerToAodTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
+ transitionFlow =
+ interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD),
+ )
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsEnrolledAndEnabled ->
+ if (isUdfpsEnrolledAndEnabled) {
+ transitionAnimation.immediatelyTransitionTo(0f)
+ } else {
+ emptyFlow()
+ }
+ }
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
+ isUdfpsEnrolledAndEnabled ->
+ if (isUdfpsEnrolledAndEnabled) {
+ transitionAnimation.createFlow(
+ duration = 300.milliseconds,
+ onStep = { it },
+ onFinish = { 1f },
+ )
+ } else {
+ emptyFlow()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..3cf793a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class PrimaryBouncerToLockscreenTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ transitionFlow =
+ interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN),
+ )
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps ->
+ if (isUdfps) {
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
new file mode 100644
index 0000000..e45d537
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Helper for flows that depend on the shade expansion */
+class ShadeDependentFlows
+@Inject
+constructor(
+ transitionInteractor: KeyguardTransitionInteractor,
+ shadeInteractor: ShadeInteractor,
+) {
+ /** When the last keyguard state transition started, was the shade fully expanded? */
+ private val lastStartedTransitionHadShadeFullyExpanded: Flow<Boolean> =
+ transitionInteractor.startedKeyguardState.sample(shadeInteractor.isAnyFullyExpanded)
+
+ /**
+ * Decide which flow to use depending on the shade expansion state at the start of the last
+ * keyguard state transition.
+ */
+ fun <T> transitionFlow(
+ flowWhenShadeIsExpanded: Flow<T>,
+ flowWhenShadeIsNotExpanded: Flow<T>,
+ ): Flow<T> {
+ val filteredFlowWhenShadeIsExpanded =
+ flowWhenShadeIsExpanded
+ .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair)
+ .filter { (_, shadeFullyExpanded) -> shadeFullyExpanded }
+ .map { (valueWhenShadeIsExpanded, _) -> valueWhenShadeIsExpanded }
+ val filteredFlowWhenShadeIsNotExpanded =
+ flowWhenShadeIsNotExpanded
+ .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair)
+ .filter { (_, shadeFullyExpanded) -> !shadeFullyExpanded }
+ .map { (valueWhenShadeIsNotExpanded, _) -> valueWhenShadeIsNotExpanded }
+ return merge(filteredFlowWhenShadeIsExpanded, filteredFlowWhenShadeIsNotExpanded)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
index c10a463..6e77e13e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
@@ -17,9 +17,9 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.Offsets
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.res.R
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,7 +35,7 @@
val context: Context,
) {
val alpha: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+ val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
val isVisible: Flow<Boolean> = alpha.map { it != 0f }
// Padding between the fingerprint icon and its bounding box in pixels.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
index 0b1079f..642904d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -19,9 +19,9 @@
import android.content.Context
import androidx.annotation.ColorInt
import com.android.settingslib.Utils.getColorAttrDefaultColor
-import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.Offsets
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -185,7 +185,7 @@
keyguardInteractor,
) {
val dozeAmount: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+ val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
// Padding between the fingerprint icon and its bounding box in pixels.
val padding: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
index 12a083e..5e19439 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -116,7 +116,7 @@
)
override fun forceUpdate() {
- forceUpdates.tryEmit(Unit)
+ tileScope.launch { forceUpdates.emit(Unit) }
}
override fun onUserChanged(user: UserHandle) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 84d2b37..404621d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,7 +34,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -83,7 +82,6 @@
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -120,7 +118,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -304,7 +301,6 @@
private final DevicePostureController mDevicePostureController;
private @DevicePostureController.DevicePostureInt int mDevicePosture;
private int mOrientation;
- private final FeatureFlags mFeatureFlags;
private final Lazy<SecureSettings> mSecureSettings;
private int mDialogTimeoutMillis;
@@ -323,9 +319,7 @@
DevicePostureController devicePostureController,
Looper looper,
DumpManager dumpManager,
- FeatureFlags featureFlags,
Lazy<SecureSettings> secureSettings) {
- mFeatureFlags = featureFlags;
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mHandler = new H(looper);
@@ -1373,14 +1367,12 @@
private void provideTouchFeedbackH(int newRingerMode) {
VibrationEffect effect = null;
- int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
switch (newRingerMode) {
case RINGER_MODE_NORMAL:
mController.scheduleTouchFeedback();
break;
case RINGER_MODE_SILENT:
effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
break;
case RINGER_MODE_VIBRATE:
// Feedback handled by onStateChange, for feedback both when user toggles
@@ -1388,11 +1380,8 @@
break;
default:
effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
- hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
}
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mDialogView.performHapticFeedback(hapticConstant);
- } else if (effect != null) {
+ if (effect != null) {
mController.vibrate(effect);
}
}
@@ -1820,22 +1809,7 @@
&& mState.ringerModeInternal != -1
&& mState.ringerModeInternal != state.ringerModeInternal
&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
-
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- if (mShowing) {
- // The dialog view is responsible for triggering haptics in the oneway API
- mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
- }
- /*
- TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
- enabled by dragging the volume slider of Settings to a value of 0. This must be
- handled by view Slices in Settings whilst using the performHapticFeedback API.
- */
-
- } else {
- // Old behavior only active if the oneway API is not used.
- mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
- }
+ mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
}
mState = state;
mDynamic.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index e3b3c21..53217d4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,7 +22,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -65,7 +64,6 @@
CsdWarningDialog.Factory csdFactory,
DevicePostureController devicePostureController,
DumpManager dumpManager,
- FeatureFlags featureFlags,
Lazy<SecureSettings> secureSettings) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
@@ -82,7 +80,6 @@
devicePostureController,
Looper.getMainLooper(),
dumpManager,
- featureFlags,
secureSettings);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
new file mode 100644
index 0000000..e8eda80
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryUdfpsInteractorTest : SysuiTestCase() {
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+ private lateinit var biometricsRepository: FakeBiometricSettingsRepository
+
+ private lateinit var underTest: DeviceEntryUdfpsInteractor
+
+ @Before
+ fun setUp() {
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+ biometricsRepository = FakeBiometricSettingsRepository()
+
+ underTest =
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = fingerprintAuthRepository,
+ biometricSettingsRepository = biometricsRepository,
+ )
+ }
+
+ @Test
+ fun udfpsSupported_rearFp_false() = runTest {
+ val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
+ fingerprintPropertyRepository.supportsRearFps()
+ assertThat(isUdfpsSupported).isFalse()
+ }
+
+ @Test
+ fun udfpsSupoprted() = runTest {
+ val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isUdfpsSupported).isTrue()
+ }
+
+ @Test
+ fun udfpsEnrolledAndEnabled() = runTest {
+ val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ assertThat(isUdfpsEnrolledAndEnabled).isTrue()
+ }
+
+ @Test
+ fun udfpsEnrolledAndEnabled_rearFp_false() = runTest {
+ val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+ fingerprintPropertyRepository.supportsRearFps()
+ biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ assertThat(isUdfpsEnrolledAndEnabled).isFalse()
+ }
+
+ @Test
+ fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() = runTest {
+ val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ assertThat(isUdfpsEnrolledAndEnabled).isFalse()
+ }
+
+ @Test
+ fun isListeningForUdfps() = runTest {
+ val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(isListeningForUdfps).isTrue()
+ }
+
+ @Test
+ fun isListeningForUdfps_rearFp_false() = runTest {
+ val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+ fingerprintPropertyRepository.supportsRearFps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(isListeningForUdfps).isFalse()
+ }
+
+ @Test
+ fun isListeningForUdfps_notRunning_false() = runTest {
+ val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(false)
+ assertThat(isListeningForUdfps).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index 5eab2fc..df52265 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -84,8 +84,8 @@
@Test
fun udfpsBurnInOffset_updatesOnResolutionScaleChange() =
testScope.runTest {
- val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset)
- val udfpsBurnInOffsetY by collectLastValue(underTest.udfpsBurnInYOffset)
+ val udfpsBurnInOffsetX by collectLastValue(underTest.deviceEntryIconXOffset)
+ val udfpsBurnInOffsetY by collectLastValue(underTest.deviceEntryIconYOffset)
assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset)
assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset)
@@ -101,7 +101,7 @@
@Test
fun udfpsBurnInProgress_updatesOnDozeTimeTick() =
testScope.runTest {
- val udfpsBurnInProgress by collectLastValue(underTest.udfpsBurnInProgress)
+ val udfpsBurnInProgress by collectLastValue(underTest.udfpsProgress)
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
setBurnInProgress(.88f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 3442df6..2dfc132 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -123,30 +123,30 @@
runCurrent()
// THEN burn in offsets are 0
- assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f)
- assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0)
- assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0)
+ assertThat(burnInOffsets?.progress).isEqualTo(0f)
+ assertThat(burnInOffsets?.y).isEqualTo(0)
+ assertThat(burnInOffsets?.x).isEqualTo(0)
// WHEN we're in the middle of the doze amount change
keyguardRepository.setDozeAmount(.50f)
runCurrent()
// THEN burn in is updated (between 0 and the full offset)
- assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f)
- assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0)
- assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0)
- assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress)
- assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset)
- assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset)
+ assertThat(burnInOffsets?.progress).isGreaterThan(0f)
+ assertThat(burnInOffsets?.y).isGreaterThan(0)
+ assertThat(burnInOffsets?.x).isGreaterThan(0)
+ assertThat(burnInOffsets?.progress).isLessThan(burnInProgress)
+ assertThat(burnInOffsets?.y).isLessThan(burnInYOffset)
+ assertThat(burnInOffsets?.x).isLessThan(burnInXOffset)
// WHEN we're fully dozing
keyguardRepository.setDozeAmount(1f)
runCurrent()
// THEN burn in offsets are updated to final current values (for the given time)
- assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress)
- assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset)
- assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
+ assertThat(burnInOffsets?.progress).isEqualTo(burnInProgress)
+ assertThat(burnInOffsets?.y).isEqualTo(burnInYOffset)
+ assertThat(burnInOffsets?.x).isEqualTo(burnInXOffset)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
index 71313c8..75bdcdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
@@ -24,12 +24,14 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
@@ -42,6 +44,7 @@
import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
+import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@ExperimentalCoroutinesApi
@@ -77,7 +80,9 @@
notificationPanelView,
featureFlags,
{ lockIconViewController },
- { DeviceEntryIconViewModel() },
+ { mock(DeviceEntryIconViewModel::class.java) },
+ { mock(DeviceEntryForegroundViewModel::class.java) },
+ { mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
new file mode 100644
index 0000000..f282481
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodToGoneTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: AodToGoneTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest = AodToGoneTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun deviceEntryParentViewHides() = runTest {
+ val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.4f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(0.8f))
+ repository.sendTransitionStep(step(1f))
+ deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ value = value,
+ transitionState = state,
+ ownerName = "AodToGoneTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..517149c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodToLockscreenTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: AodToLockscreenTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ underTest =
+ AodToLockscreenTransitionViewModel(
+ interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor =
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(),
+ biometricSettingsRepository = FakeBiometricSettingsRepository(),
+ ),
+ )
+ }
+
+ @Test
+ fun deviceEntryParentViewShows() = runTest {
+ val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+ deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ @Test
+ fun deviceEntryBackgroundView_udfps_alphaFadeIn() = runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ val deviceEntryBackgroundViewAlpha by
+ collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+
+ // fade in
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.1f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.2f)
+
+ repository.sendTransitionStep(step(0.3f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.6f)
+
+ repository.sendTransitionStep(step(0.6f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryBackgroundView_rearFp_noUpdates() = runTest {
+ fingerprintPropertyRepository.supportsRearFps()
+ val deviceEntryBackgroundViewAlpha by
+ collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ // no updates
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryBackgroundViewAlpha).isNull()
+ repository.sendTransitionStep(step(0.1f))
+ assertThat(deviceEntryBackgroundViewAlpha).isNull()
+ repository.sendTransitionStep(step(0.3f))
+ assertThat(deviceEntryBackgroundViewAlpha).isNull()
+ repository.sendTransitionStep(step(0.6f))
+ assertThat(deviceEntryBackgroundViewAlpha).isNull()
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryBackgroundViewAlpha).isNull()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = "AodToLockscreenTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
new file mode 100644
index 0000000..96f69462
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodToOccludedTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: AodToOccludedTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest = AodToOccludedTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun deviceEntryParentViewHides() = runTest {
+ val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.4f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(0.8f))
+ repository.sendTransitionStep(step(1f))
+ deviceEntryParentViewAlpha.forEach { Truth.assertThat(it).isEqualTo(0f) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
+ value = value,
+ transitionState = state,
+ ownerName = "AodToOccludedTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..5dccc3b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DozingToLockscreenTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var testScope: TestScope
+ private lateinit var underTest: DozingToLockscreenTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ underTest =
+ DozingToLockscreenTransitionViewModel(
+ interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor,
+ )
+ }
+
+ @Test
+ fun deviceEntryParentViewShows() = runTest {
+ val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+ deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = "DozingToLockscreenTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 6d47aed..fd125e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -19,6 +19,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
@@ -35,6 +41,7 @@
import com.android.systemui.util.mockito.mock
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -44,22 +51,34 @@
import org.junit.Test
import org.junit.runner.RunWith
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
val interactor =
KeyguardTransitionInteractorFactory.create(
scope = TestScope().backgroundScope,
repository = repository,
)
.keyguardTransitionInteractor
- underTest = DreamingToLockscreenTransitionViewModel(interactor, mock())
+ underTest =
+ DreamingToLockscreenTransitionViewModel(
+ interactor,
+ mock(),
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(),
+ biometricSettingsRepository = FakeBiometricSettingsRepository(),
+ ),
+ )
}
@Test
@@ -129,6 +148,78 @@
}
@Test
+ fun deviceEntryParentViewFadeIn() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, STARTED))
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.2f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewAppear() =
+ runTest(UnconfinedTestDispatcher()) {
+ fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations = emptyMap(),
+ )
+ val values = mutableListOf<Float>()
+
+ val job =
+ underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, STARTED))
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.2f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(1f))
+
+ values.forEach { assertThat(it).isEqualTo(1f) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun deviceEntryBackground_noUdfps_noUpdates() =
+ runTest(UnconfinedTestDispatcher()) {
+ fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations = emptyMap(),
+ )
+ val values = mutableListOf<Float>()
+
+ val job =
+ underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, STARTED))
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.2f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(0) // no updates
+
+ job.cancel()
+ }
+
+ @Test
fun lockscreenTranslationY() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
index 255f4df..c1444a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -19,7 +19,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -27,6 +31,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -34,11 +39,14 @@
import org.junit.Test
import org.junit.runner.RunWith
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class GoneToAodTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: GoneToAodTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var testScope: TestScope
@Before
@@ -47,13 +55,24 @@
testScope = TestScope(testDispatcher)
repository = FakeKeyguardTransitionRepository()
- val interactor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = repository,
- )
- .keyguardTransitionInteractor
- underTest = GoneToAodTransitionViewModel(interactor)
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+
+ underTest =
+ GoneToAodTransitionViewModel(
+ interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor =
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(),
+ biometricSettingsRepository = biometricSettingsRepository,
+ ),
+ )
}
@Test
@@ -63,11 +82,11 @@
val enterFromTopTranslationY by
collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
- // The animation should only start > halfway through
+ // The animation should only start > .4f way through
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
assertThat(enterFromTopTranslationY).isEqualTo(pixels)
- repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.4f))
assertThat(enterFromTopTranslationY).isEqualTo(pixels)
repository.sendTransitionStep(step(.85f))
@@ -83,11 +102,11 @@
testScope.runTest {
val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
- // The animation should only start > halfway through
+ // The animation should only start > .4f way through
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
- repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.4f))
assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
repository.sendTransitionStep(step(.85f))
@@ -97,6 +116,98 @@
assertThat(enterFromTopAnimationAlpha).isEqualTo(1f)
}
+ @Test
+ fun deviceEntryBackgroundViewAlpha() =
+ testScope.runTest {
+ val deviceEntryBackgroundViewAlpha by
+ collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+
+ // immediately 0f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.4f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsEnrolled() =
+ testScope.runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // animation doesn't start until the end
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.95f))
+ assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.01f, 1f))
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.99f, 1f))
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_rearFpEnrolled() =
+ testScope.runTest {
+ fingerprintPropertyRepository.supportsRearFps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // animation doesn't start until the end
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(.95f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() =
+ testScope.runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // animation doesn't start until the end
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(.95f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..4074851
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.collectLastValue
+import com.android.collectValues
+import com.android.runCurrent
+import com.android.runTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenToAodTransitionViewModelTest : SysuiTestCase() {
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ UserDomainLayerModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<LockscreenToAodTransitionViewModel> {
+ val repository: FakeKeyguardTransitionRepository
+ val deviceEntryRepository: FakeDeviceEntryRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val shadeRepository: FakeShadeRepository
+ val fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ val biometricSettingsRepository: FakeBiometricSettingsRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+
+ fun shadeExpanded(expanded: Boolean) {
+ if (expanded) {
+ shadeRepository.setQsExpansion(1f)
+ } else {
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeRepository.setQsExpansion(0f)
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ }
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerLockscreenToAodTransitionViewModelTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(FACE_AUTH_REFACTOR, true)
+ set(FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks = TestMocksModule(),
+ )
+
+ @Test
+ fun backgroundViewAlpha_shadeNotExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ shadeExpanded(false)
+ runCurrent()
+
+ // fade out
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(actual).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.3f))
+ assertThat(actual).isIn(Range.closed(.1f, .9f))
+
+ // finish fading out before the end of the full transition
+ repository.sendTransitionStep(step(.7f))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(0f)
+ }
+
+ @Test
+ fun backgroundViewAlpha_shadeExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ shadeExpanded(true)
+ runCurrent()
+
+ // immediately 0f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.3f))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.7f))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(0f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeNotExpanded() =
+ testComponent.runTest {
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ shadeExpanded(false)
+ runCurrent()
+
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(.3f),
+ step(.7f),
+ step(1f),
+ ),
+ testScope = testScope,
+ )
+ // immediately 1f
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ shadeExpanded(true)
+ runCurrent()
+
+ // fade in
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.3f))
+ assertThat(actual).isIn(Range.closed(.1f, .9f))
+
+ // finish fading in before the end of the full transition
+ repository.sendTransitionStep(step(.7f))
+ assertThat(actual).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_rearFp_shadeNotExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ fingerprintPropertyRepository.supportsRearFps()
+ shadeExpanded(false)
+ runCurrent()
+
+ // fade out
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(actual).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.1f))
+ assertThat(actual).isIn(Range.closed(.1f, .9f))
+
+ // finish fading out before the end of the full transition
+ repository.sendTransitionStep(step(.7f))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(0f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_rearFp_shadeExpanded() =
+ testComponent.runTest {
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+ fingerprintPropertyRepository.supportsRearFps()
+ shadeExpanded(true)
+ runCurrent()
+
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(.3f),
+ step(.7f),
+ step(1f),
+ ),
+ testScope = testScope,
+ )
+ // immediately 0f
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "LockscreenToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 89a1d2b..5c85357 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,88 +18,172 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.collectLastValue
+import com.android.collectValues
+import com.android.runCurrent
+import com.android.runTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import dagger.BindsInstance
+import dagger.Component
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
- private lateinit var underTest: LockscreenToDreamingTransitionViewModel
- private lateinit var repository: FakeKeyguardTransitionRepository
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ UserDomainLayerModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<LockscreenToDreamingTransitionViewModel> {
+ val repository: FakeKeyguardTransitionRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val shadeRepository: FakeShadeRepository
- @Before
- fun setUp() {
- repository = FakeKeyguardTransitionRepository()
- val interactor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = repository,
- )
- .keyguardTransitionInteractor
- underTest = LockscreenToDreamingTransitionViewModel(interactor)
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+
+ fun shadeExpanded(expanded: Boolean) {
+ if (expanded) {
+ shadeRepository.setQsExpansion(1f)
+ } else {
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeRepository.setQsExpansion(0f)
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ }
+ }
}
+ private val testComponent: TestComponent =
+ DaggerLockscreenToDreamingTransitionViewModelTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks = TestMocksModule(),
+ )
@Test
fun lockscreenFadeOut() =
- runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
-
- val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
-
- // Should start running here...
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0f))
- repository.sendTransitionStep(step(0.1f))
- repository.sendTransitionStep(step(0.2f))
- repository.sendTransitionStep(step(0.3f))
- // ...up to here
- repository.sendTransitionStep(step(1f))
+ testComponent.runTest {
+ val values by collectValues(underTest.lockscreenAlpha)
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED), // Should start running here...
+ step(0f),
+ step(.1f),
+ step(.2f),
+ step(.3f), // ...up to here
+ step(1f),
+ ),
+ testScope = testScope,
+ )
// Only three values should be present, since the dream overlay runs for a small
// fraction of the overall animation time
assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
-
- job.cancel()
}
@Test
fun lockscreenTranslationY() =
- runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
-
+ testComponent.runTest {
val pixels = 100
- val job =
- underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ val values by collectValues(underTest.lockscreenTranslationY(pixels))
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0f))
- repository.sendTransitionStep(step(0.3f))
- repository.sendTransitionStep(step(0.5f))
- repository.sendTransitionStep(step(1f))
- // And a final reset event on FINISHED
- repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED), // Should start running here...
+ step(0f),
+ step(.3f),
+ step(.5f),
+ step(1f),
+ step(1f, TransitionState.FINISHED), // Final reset event on FINISHED
+ ),
+ testScope = testScope,
+ )
assertThat(values.size).isEqualTo(6)
values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
// Validate finished value
assertThat(values[5]).isEqualTo(0f)
+ }
- job.cancel()
+ @Test
+ fun deviceEntryParentViewAlpha_shadeExpanded() =
+ testComponent.runTest {
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+ shadeExpanded(true)
+ runCurrent()
+
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(.3f),
+ step(.5f),
+ step(1f),
+ step(1f, TransitionState.FINISHED),
+ ),
+ testScope = testScope,
+ )
+
+ // immediately 0f
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_shadeNotExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ shadeExpanded(false)
+ runCurrent()
+
+ // fade out
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(actual).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.1f))
+ assertThat(actual).isIn(Range.open(.1f, .9f))
+
+ // alpha is 1f before the full transition starts ending
+ repository.sendTransitionStep(step(0.8f))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(0f)
}
private fun step(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
new file mode 100644
index 0000000..1494c92
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenToGoneTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: LockscreenToGoneTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest =
+ LockscreenToGoneTransitionViewModel(
+ interactor,
+ )
+ }
+
+ @Test
+ fun deviceEntryParentViewHides() = runTest {
+ val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.4f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(0.8f))
+ repository.sendTransitionStep(step(1f))
+ deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = value,
+ transitionState = state,
+ ownerName = "LockscreenToGoneTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 41f8856..4cbefa3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,109 +18,185 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.collectLastValue
+import com.android.collectValues
+import com.android.runCurrent
+import com.android.runTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
+import dagger.BindsInstance
+import dagger.Component
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
- private lateinit var underTest: LockscreenToOccludedTransitionViewModel
- private lateinit var repository: FakeKeyguardTransitionRepository
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ UserDomainLayerModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<LockscreenToOccludedTransitionViewModel> {
+ val repository: FakeKeyguardTransitionRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val shadeRepository: FakeShadeRepository
- @Before
- fun setUp() {
- repository = FakeKeyguardTransitionRepository()
- val interactor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = repository,
- )
- .keyguardTransitionInteractor
- underTest = LockscreenToOccludedTransitionViewModel(interactor)
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+
+ fun shadeExpanded(expanded: Boolean) {
+ if (expanded) {
+ shadeRepository.setQsExpansion(1f)
+ } else {
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeRepository.setQsExpansion(0f)
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ }
+ }
}
+ private val testComponent: TestComponent =
+ DaggerLockscreenToOccludedTransitionViewModelTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks = TestMocksModule(),
+ )
+
@Test
fun lockscreenFadeOut() =
- runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
-
- val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
-
- // Should start running here...
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0f))
- repository.sendTransitionStep(step(0.1f))
- repository.sendTransitionStep(step(0.4f))
- repository.sendTransitionStep(step(0.7f))
- // ...up to here
- repository.sendTransitionStep(step(1f))
-
+ testComponent.runTest {
+ val values by collectValues(underTest.lockscreenAlpha)
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED), // Should start running here...
+ step(0f),
+ step(.1f),
+ step(.4f),
+ step(.7f), // ...up to here
+ step(1f),
+ ),
+ testScope = testScope,
+ )
// Only 3 values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
-
- job.cancel()
}
@Test
fun lockscreenTranslationY() =
- runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
-
+ testComponent.runTest {
val pixels = 100
- val job =
- underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
-
- // Should start running here...
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0f))
- repository.sendTransitionStep(step(0.3f))
- repository.sendTransitionStep(step(0.5f))
- repository.sendTransitionStep(step(1f))
- // ...up to here
-
+ val values by collectValues(underTest.lockscreenTranslationY(pixels))
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED), // Should start running here...
+ step(0f),
+ step(.3f),
+ step(.5f),
+ step(1f), // ...up to here
+ ),
+ testScope = testScope,
+ )
assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
-
- job.cancel()
}
@Test
fun lockscreenTranslationYIsCanceled() =
- runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
-
+ testComponent.runTest {
val pixels = 100
- val job =
- underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
-
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0f))
- repository.sendTransitionStep(step(0.3f))
- repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED))
-
+ val values by collectValues(underTest.lockscreenTranslationY(pixels))
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(.3f),
+ step(0.3f, TransitionState.CANCELED),
+ ),
+ testScope = testScope,
+ )
assertThat(values.size).isEqualTo(4)
values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
// Cancel will reset the translation
assertThat(values[3]).isEqualTo(0)
+ }
- job.cancel()
+ @Test
+ fun deviceEntryParentViewAlpha_shadeExpanded() =
+ testComponent.runTest {
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+ shadeExpanded(true)
+ runCurrent()
+
+ // immediately 0f
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(.5f),
+ step(1f, TransitionState.FINISHED)
+ ),
+ testScope = testScope,
+ )
+
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_shadeNotExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ shadeExpanded(false)
+ runCurrent()
+
+ // fade out
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(actual).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.2f))
+ assertThat(actual).isIn(Range.open(.1f, .9f))
+
+ // alpha is 1f before the full transition starts ending
+ repository.sendTransitionStep(step(0.8f))
+ assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(0f)
}
private fun step(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
new file mode 100644
index 0000000..4f56435
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.collectLastValue
+import com.android.runCurrent
+import com.android.runTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
+import com.google.common.collect.Range
+import com.google.common.truth.Truth
+import dagger.BindsInstance
+import dagger.Component
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ UserDomainLayerModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<LockscreenToPrimaryBouncerTransitionViewModel> {
+ val repository: FakeKeyguardTransitionRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val shadeRepository: FakeShadeRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+
+ fun shadeExpanded(expanded: Boolean) {
+ if (expanded) {
+ shadeRepository.setQsExpansion(1f)
+ } else {
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeRepository.setQsExpansion(0f)
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ }
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerLockscreenToPrimaryBouncerTransitionViewModelTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks = TestMocksModule(),
+ )
+
+ @Test
+ fun deviceEntryParentViewAlpha_shadeExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ shadeExpanded(true)
+ runCurrent()
+
+ // immediately 0f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ runCurrent()
+ Truth.assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.2f))
+ runCurrent()
+ Truth.assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.8f))
+ runCurrent()
+ Truth.assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ runCurrent()
+ Truth.assertThat(actual).isEqualTo(0f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_shadeNotExpanded() =
+ testComponent.runTest {
+ val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ shadeExpanded(false)
+ runCurrent()
+
+ // fade out
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ runCurrent()
+ Truth.assertThat(actual).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.1f))
+ runCurrent()
+ Truth.assertThat(actual).isIn(Range.open(.1f, .9f))
+
+ // alpha is 1f before the full transition starts ending
+ repository.sendTransitionStep(step(0.8f))
+ runCurrent()
+ Truth.assertThat(actual).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ runCurrent()
+ Truth.assertThat(actual).isEqualTo(0f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING,
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = value,
+ transitionState = state,
+ ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..0eb8ff6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OccludedToAodTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: OccludedToAodTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+
+ underTest =
+ OccludedToAodTransitionViewModel(
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor,
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(),
+ biometricSettingsRepository = biometricSettingsRepository,
+ ),
+ )
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewAlpha() = runTest {
+ val deviceEntryBackgroundViewAlpha by
+ collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+
+ // immediately 0f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.4f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsEnrolled() = runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // immediately 1f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.95f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest {
+ fingerprintPropertyRepository.supportsRearFps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // no updates
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(.95f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // no updates
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(.95f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "OccludedToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index ec95cb8..d077227 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -19,6 +19,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -26,6 +30,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -35,22 +40,35 @@
import org.junit.Test
import org.junit.runner.RunWith
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: OccludedToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = repository,
- )
- .keyguardTransitionInteractor
- underTest = OccludedToLockscreenTransitionViewModel(interactor)
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ underTest =
+ OccludedToLockscreenTransitionViewModel(
+ interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor =
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(),
+ biometricSettingsRepository = biometricSettingsRepository,
+ ),
+ )
}
@Test
@@ -113,6 +131,78 @@
job.cancel()
}
+ @Test
+ fun deviceEntryParentViewFadeIn() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ // Should start running here...
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.4f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.8f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewShows() =
+ runTest(UnconfinedTestDispatcher()) {
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val values = mutableListOf<Float>()
+
+ val job =
+ underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.4f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(0.8f))
+ repository.sendTransitionStep(step(1f))
+
+ values.forEach { assertThat(it).isEqualTo(1f) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun deviceEntryBackgroundView_noUdfpsEnrolled_noUpdates() =
+ runTest(UnconfinedTestDispatcher()) {
+ fingerprintPropertyRepository.supportsRearFps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val values = mutableListOf<Float>()
+
+ val job =
+ underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.4f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(0.8f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values).isEmpty() // no updates
+
+ job.cancel()
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..350b310
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerToAodTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: PrimaryBouncerToAodTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest =
+ PrimaryBouncerToAodTransitionViewModel(
+ interactor,
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(),
+ biometricSettingsRepository = biometricSettingsRepository,
+ ),
+ )
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewAlpha() = runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ val deviceEntryBackgroundViewAlpha by
+ collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+
+ // immediately 0f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.4f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsEnrolled_fadeIn() = runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(.75f))
+ repository.sendTransitionStep(step(1f))
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest {
+ fingerprintPropertyRepository.supportsRearFps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // animation doesn't start until the end
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(.95f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(.75f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(deviceEntryParentViewAlpha).isNull()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "PrimaryBouncerToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..24e4920
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: PrimaryBouncerToLockscreenTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+
+ underTest =
+ PrimaryBouncerToLockscreenTransitionViewModel(
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor,
+ DeviceEntryUdfpsInteractor(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(),
+ biometricSettingsRepository = biometricSettingsRepository,
+ ),
+ )
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha() = runTest {
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // immediately 1f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(0.4f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewAlpha_udfpsEnrolled_show() = runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+
+ // immediately 1f
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(0.1f))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.3f))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(.5f))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewAlpha_rearFpEnrolled_noUpdates() = runTest {
+ fingerprintPropertyRepository.supportsRearFps()
+ val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(bgViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(bgViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(.75f))
+ assertThat(bgViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(bgViewAlpha).isNull()
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(bgViewAlpha).isNull()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
deleted file mode 100644
index d3b7daa..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.tiles.viewmodel
-
-import android.os.UserHandle
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
-import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
-import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
-import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.base.logging.QSTileLogger
-import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-// TODO(b/299909368): Add more tests
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
-
- @Mock private lateinit var qsTileLogger: QSTileLogger
- @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
-
- private val fakeUserRepository = FakeUserRepository()
- private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
- private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
- private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
- private val fakeFalsingManager = FalsingManagerFake()
-
- private val testCoroutineDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testCoroutineDispatcher)
-
- private lateinit var underTest: QSTileViewModel
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- underTest = createViewModel(testScope)
- }
-
- @Test
- fun testDoesntListenStateUntilCreated() =
- testScope.runTest {
- assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
-
- assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
-
- underTest.state.launchIn(backgroundScope)
- runCurrent()
-
- assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
- assertThat(fakeQSTileDataInteractor.dataRequests.first())
- .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0)))
- }
-
- private fun createViewModel(
- scope: TestScope,
- config: QSTileConfig = TEST_QS_TILE_CONFIG,
- ): QSTileViewModel =
- QSTileViewModelImpl(
- config,
- { fakeQSTileUserActionInteractor },
- { fakeQSTileDataInteractor },
- {
- object : QSTileDataToStateMapper<Any> {
- override fun map(config: QSTileConfig, data: Any): QSTileState =
- QSTileState.build(
- { Icon.Resource(0, ContentDescription.Resource(0)) },
- ""
- ) {}
- }
- },
- fakeDisabledByPolicyInteractor,
- fakeUserRepository,
- fakeFalsingManager,
- qsTileAnalytics,
- qsTileLogger,
- FakeSystemClock(),
- testCoroutineDispatcher,
- scope.backgroundScope,
- )
-
- private companion object {
-
- val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {}
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
new file mode 100644
index 0000000..3a0ebdb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSTileViewModelTest : SysuiTestCase() {
+
+ @Mock private lateinit var qsTileLogger: QSTileLogger
+ @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+
+ private val tileConfig =
+ QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") }
+
+ private val userRepository = FakeUserRepository()
+ private val tileDataInteractor = FakeQSTileDataInteractor<String>()
+ private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>()
+ private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
+ private val falsingManager = FalsingManagerFake()
+
+ private val testCoroutineDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testCoroutineDispatcher)
+
+ private lateinit var underTest: QSTileViewModel
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = createViewModel(testScope)
+ }
+
+ @Test
+ fun stateReceivedForTheData() =
+ testScope.runTest {
+ val testTileData = "test_tile_data"
+ val states = collectValues(underTest.state)
+ runCurrent()
+
+ tileDataInteractor.emitData(testTileData)
+ runCurrent()
+
+ assertThat(states()).isNotEmpty()
+ assertThat(states().first().label).isEqualTo(testTileData)
+ verify(qsTileLogger).logInitialRequest(eq(tileConfig.tileSpec))
+ }
+
+ @Test
+ fun doesntListenDataIfStateIsntListened() =
+ testScope.runTest {
+ assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0)
+
+ underTest.state.launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(1)
+ }
+
+ @Test
+ fun doesntListenAvailabilityIfAvailabilityIsntListened() =
+ testScope.runTest {
+ assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0)
+
+ underTest.isAvailable.launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(1)
+ }
+
+ @Test
+ fun doesntListedDataAfterDestroy() =
+ testScope.runTest {
+ underTest.state.launchIn(backgroundScope)
+ underTest.isAvailable.launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.destroy()
+ runCurrent()
+
+ assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0)
+ assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0)
+ }
+
+ @Test
+ fun forceUpdateTriggersData() =
+ testScope.runTest {
+ underTest.state.launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.forceUpdate()
+ runCurrent()
+
+ assertThat(tileDataInteractor.triggers.last())
+ .isInstanceOf(DataUpdateTrigger.ForceUpdate::class.java)
+ verify(qsTileLogger).logForceUpdate(eq(tileConfig.tileSpec))
+ }
+
+ @Test
+ fun userChangeUpdatesData() =
+ testScope.runTest {
+ underTest.state.launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.onUserChanged(USER)
+ runCurrent()
+
+ assertThat(tileDataInteractor.dataRequests.last())
+ .isEqualTo(FakeQSTileDataInteractor.DataRequest(USER))
+ }
+
+ @Test
+ fun userChangeUpdatesAvailability() =
+ testScope.runTest {
+ underTest.isAvailable.launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.onUserChanged(USER)
+ runCurrent()
+
+ assertThat(tileDataInteractor.availabilityRequests.last())
+ .isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER))
+ }
+
+ private fun createViewModel(
+ scope: TestScope,
+ config: QSTileConfig = tileConfig,
+ ): QSTileViewModel =
+ QSTileViewModelImpl(
+ config,
+ { tileUserActionInteractor },
+ { tileDataInteractor },
+ {
+ object : QSTileDataToStateMapper<String> {
+ override fun map(config: QSTileConfig, data: String): QSTileState =
+ QSTileState.build(
+ { Icon.Resource(0, ContentDescription.Resource(0)) },
+ data
+ ) {}
+ }
+ },
+ disabledByPolicyInteractor,
+ userRepository,
+ falsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
+ FakeSystemClock(),
+ testCoroutineDispatcher,
+ scope.backgroundScope,
+ )
+
+ private companion object {
+
+ val USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
new file mode 100644
index 0000000..ea8acc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import androidx.test.filters.MediumTest
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Tests all possible [QSTileUserAction]s. If you need */
+@MediumTest
+@RunWith(Parameterized::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSTileViewModelUserInputTest : SysuiTestCase() {
+
+ @Mock private lateinit var qsTileLogger: QSTileLogger
+ @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+
+ @Parameter lateinit var userAction: QSTileUserAction
+
+ private val tileConfig =
+ QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") }
+
+ private val userRepository = FakeUserRepository()
+ private val tileDataInteractor = FakeQSTileDataInteractor<String>()
+ private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>()
+ private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
+ private val falsingManager = FalsingManagerFake()
+
+ private val testCoroutineDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testCoroutineDispatcher)
+
+ private lateinit var underTest: QSTileViewModel
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = createViewModel(testScope)
+ }
+
+ @Test
+ fun userInputTriggersData() =
+ testScope.runTest {
+ tileDataInteractor.emitData("initial_data")
+ underTest.state.launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.onActionPerformed(userAction)
+ runCurrent()
+
+ assertThat(tileDataInteractor.triggers.last())
+ .isInstanceOf(DataUpdateTrigger.UserInput::class.java)
+ verify(qsTileLogger)
+ .logUserAction(eq(userAction), eq(tileConfig.tileSpec), eq(true), eq(true))
+ verify(qsTileLogger)
+ .logUserActionPipeline(
+ eq(tileConfig.tileSpec),
+ eq(userAction),
+ any(),
+ eq("initial_data")
+ )
+ verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction))
+ }
+
+ @Test
+ fun disabledByPolicyUserInputIsSkipped() =
+ testScope.runTest {
+ underTest.state.launchIn(backgroundScope)
+ disabledByPolicyInteractor.policyResult =
+ DisabledByPolicyInteractor.PolicyResult.TileDisabled(
+ RestrictedLockUtils.EnforcedAdmin()
+ )
+ runCurrent()
+
+ underTest.onActionPerformed(userAction)
+ runCurrent()
+
+ assertThat(tileDataInteractor.triggers.last())
+ .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java)
+ verify(qsTileLogger)
+ .logUserActionRejectedByPolicy(eq(userAction), eq(tileConfig.tileSpec))
+ verify(qsTileAnalytics, never()).trackUserAction(any(), any())
+ }
+
+ @Test
+ fun falsedUserInputIsSkipped() =
+ testScope.runTest {
+ underTest.state.launchIn(backgroundScope)
+ falsingManager.setFalseLongTap(true)
+ falsingManager.setFalseTap(true)
+ runCurrent()
+
+ underTest.onActionPerformed(userAction)
+ runCurrent()
+
+ assertThat(tileDataInteractor.triggers.last())
+ .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java)
+ verify(qsTileLogger)
+ .logUserActionRejectedByFalsing(eq(userAction), eq(tileConfig.tileSpec))
+ verify(qsTileAnalytics, never()).trackUserAction(any(), any())
+ }
+
+ @Test
+ fun userInputIsThrottled() =
+ testScope.runTest {
+ val inputCount = 100
+ underTest.state.launchIn(backgroundScope)
+
+ repeat(inputCount) { underTest.onActionPerformed(userAction) }
+ runCurrent()
+
+ assertThat(tileDataInteractor.triggers.size).isLessThan(inputCount)
+ }
+
+ private fun createViewModel(scope: TestScope): QSTileViewModel =
+ QSTileViewModelImpl(
+ tileConfig,
+ { tileUserActionInteractor },
+ { tileDataInteractor },
+ {
+ object : QSTileDataToStateMapper<String> {
+ override fun map(config: QSTileConfig, data: String): QSTileState =
+ QSTileState.build(
+ { Icon.Resource(0, ContentDescription.Resource(0)) },
+ data
+ ) {}
+ }
+ },
+ disabledByPolicyInteractor,
+ userRepository,
+ falsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
+ FakeSystemClock(),
+ testCoroutineDispatcher,
+ scope.backgroundScope,
+ )
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Iterable<QSTileUserAction> =
+ listOf(
+ QSTileUserAction.Click(null),
+ QSTileUserAction.LongClick(null),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 7456e00..8c823b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,7 +20,6 @@
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -68,7 +67,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
@@ -149,14 +147,13 @@
}
};
- private FakeFeatureFlags mFeatureFlags;
private int mLongestHideShowAnimationDuration = 250;
private FakeSettings mSecureSettings;
@Rule
public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
- @Before
+ @Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -179,8 +176,6 @@
mConfigurationController = new FakeConfigurationController();
- mFeatureFlags = new FakeFeatureFlags();
-
mSecureSettings = new FakeSettings();
when(mLazySecureSettings.get()).thenReturn(mSecureSettings);
@@ -200,7 +195,6 @@
mPostureController,
mTestableLooper.getLooper(),
mDumpManager,
- mFeatureFlags,
mLazySecureSettings);
mDialog.init(0, null);
State state = createShellState();
@@ -328,7 +322,6 @@
@Test
public void testVibrateOnRingerChangedToVibrate() {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialSilentState = new State();
initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
@@ -349,30 +342,7 @@
}
@Test
- public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- final State initialSilentState = new State();
- initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
-
- final State vibrateState = new State();
- vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
-
- // change ringer to silent
- mDialog.onStateChangedH(initialSilentState);
-
- // expected: shouldn't call vibrate yet
- verify(mVolumeDialogController, never()).vibrate(any());
-
- // changed ringer to vibrate
- mDialog.onStateChangedH(vibrateState);
-
- // expected: vibrate method of controller is not used
- verify(mVolumeDialogController, never()).vibrate(any());
- }
-
- @Test
public void testNoVibrateOnRingerInitialization() {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = -1;
@@ -390,29 +360,9 @@
}
@Test
- public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- final State initialUnsetState = new State();
- initialUnsetState.ringerModeInternal = -1;
-
- // ringer not initialized yet:
- mDialog.onStateChangedH(initialUnsetState);
-
- final State vibrateState = new State();
- vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
-
- // changed ringer to vibrate
- mDialog.onStateChangedH(vibrateState);
-
- // shouldn't call vibrate on the controller either
- verify(mVolumeDialogController, never()).vibrate(any());
- }
-
- @Test
public void testSelectVibrateFromDrawer() {
assumeHasDrawer();
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -426,27 +376,9 @@
}
@Test
- public void testSelectVibrateFromDrawer_OnewayAPI_On() {
- assumeHasDrawer();
-
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- final State initialUnsetState = new State();
- initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
- mDialog.onStateChangedH(initialUnsetState);
-
- mActiveRinger.performClick();
- mDrawerVibrate.performClick();
-
- // Make sure we've actually changed the ringer mode.
- verify(mVolumeDialogController, times(1)).setRingerMode(
- AudioManager.RINGER_MODE_VIBRATE, false);
- }
-
- @Test
public void testSelectMuteFromDrawer() {
assumeHasDrawer();
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -460,27 +392,9 @@
}
@Test
- public void testSelectMuteFromDrawer_OnewayAPI_On() {
- assumeHasDrawer();
-
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- final State initialUnsetState = new State();
- initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
- mDialog.onStateChangedH(initialUnsetState);
-
- mActiveRinger.performClick();
- mDrawerMute.performClick();
-
- // Make sure we've actually changed the ringer mode.
- verify(mVolumeDialogController, times(1)).setRingerMode(
- AudioManager.RINGER_MODE_SILENT, false);
- }
-
- @Test
public void testSelectNormalFromDrawer() {
assumeHasDrawer();
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
mDialog.onStateChangedH(initialUnsetState);
@@ -493,23 +407,6 @@
AudioManager.RINGER_MODE_NORMAL, false);
}
- @Test
- public void testSelectNormalFromDrawer_OnewayAPI_On() {
- assumeHasDrawer();
-
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- final State initialUnsetState = new State();
- initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
- mDialog.onStateChangedH(initialUnsetState);
-
- mActiveRinger.performClick();
- mDrawerNormal.performClick();
-
- // Make sure we've actually changed the ringer mode.
- verify(mVolumeDialogController, times(1)).setRingerMode(
- RINGER_MODE_NORMAL, false);
- }
-
/**
* Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
* API does not exist. So we do the next best thing; we check the cached icon id.
@@ -682,7 +579,6 @@
State state = createShellState();
state.ringerModeInternal = ringerMode;
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
mDialog.onStateChangedH(state);
mDialog.show(SHOW_REASON_UNKNOWN);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 0c5e438..005cac4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -19,10 +19,15 @@
import android.hardware.biometrics.SensorLocationInternal
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
+@SysUISingleton
+class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPropertyRepository {
private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
override val sensorId = _sensorId.asStateFlow()
@@ -50,4 +55,29 @@
_sensorType.value = sensorType
_sensorLocations.value = sensorLocations
}
+
+ /** setProperties as if the device supports UDFPS_OPTICAL. */
+ fun supportsUdfps() {
+ setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations = emptyMap(),
+ )
+ }
+
+ /** setProperties as if the device supports the rear fingerprint sensor. */
+ fun supportsRearFps() {
+ setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations = emptyMap(),
+ )
+ }
+}
+
+@Module
+interface FakeFingerprintPropertyRepositoryModule {
+ @Binds fun bindFake(fake: FakeFingerprintPropertyRepository): FingerprintPropertyRepository
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index 44286b7..8ff04a63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -15,17 +15,23 @@
package com.android.systemui.deviceentry.data
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeTrustRepositoryModule
import dagger.Module
@Module(
includes =
[
+ FakeBiometricSettingsRepositoryModule::class,
FakeDeviceEntryRepositoryModule::class,
- FakeTrustRepositoryModule::class,
FakeDeviceEntryFaceAuthRepositoryModule::class,
+ FakeDeviceEntryFingerprintAuthRepositoryModule::class,
+ FakeFingerprintPropertyRepositoryModule::class,
+ FakeTrustRepositoryModule::class,
]
)
object FakeDeviceEntryDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index 85261123..df31a12 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -18,13 +18,18 @@
package com.android.systemui.keyguard.data.repository
import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
-class FakeBiometricSettingsRepository : BiometricSettingsRepository {
+@SysUISingleton
+class FakeBiometricSettingsRepository @Inject constructor() : BiometricSettingsRepository {
private val _isFingerprintEnrolledAndEnabled = MutableStateFlow(false)
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
get() = _isFingerprintEnrolledAndEnabled
@@ -97,3 +102,8 @@
}
}
}
+
+@Module
+interface FakeBiometricSettingsRepositoryModule {
+ @Binds fun bindFake(fake: FakeBiometricSettingsRepository): BiometricSettingsRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 38791ca..c9160ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,14 +17,20 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
-class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
+@SysUISingleton
+class FakeDeviceEntryFingerprintAuthRepository @Inject constructor() :
+ DeviceEntryFingerprintAuthRepository {
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
fun setLockedOut(lockedOut: Boolean) {
@@ -52,3 +58,11 @@
_authenticationStatus.value = status
}
}
+
+@Module
+interface FakeDeviceEntryFingerprintAuthRepositoryModule {
+ @Binds
+ fun bindFake(
+ fake: FakeDeviceEntryFingerprintAuthRepository
+ ): DeviceEntryFingerprintAuthRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index b90ad8c..3674244 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -151,6 +151,17 @@
_transitions.emit(step)
}
+ suspend fun sendTransitionSteps(
+ steps: List<TransitionStep>,
+ testScope: TestScope,
+ validateStep: Boolean = true
+ ) {
+ steps.forEach {
+ sendTransitionStep(it, validateStep = validateStep)
+ testScope.testScheduler.runCurrent()
+ }
+ }
+
override fun startTransition(info: TransitionInfo): UUID? {
return if (info.animator == null) UUID.randomUUID() else null
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
index 1efa74b..62765d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
@@ -20,7 +20,6 @@
class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
- var handleResult: Boolean = false
var policyResult: DisabledByPolicyInteractor.PolicyResult =
DisabledByPolicyInteractor.PolicyResult.TileEnabled
@@ -31,5 +30,9 @@
override fun handlePolicyResult(
policyResult: DisabledByPolicyInteractor.PolicyResult
- ): Boolean = handleResult
+ ): Boolean =
+ when (policyResult) {
+ is DisabledByPolicyInteractor.PolicyResult.TileEnabled -> false
+ is DisabledByPolicyInteractor.PolicyResult.TileDisabled -> true
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
index 2b3330f..3fcf8a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -17,16 +17,21 @@
package com.android.systemui.qs.tiles.base.interactor
import android.os.UserHandle
-import javax.annotation.CheckReturnValue
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.flatMapLatest
-class FakeQSTileDataInteractor<T>(
- private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE),
- private val availabilityFlow: MutableSharedFlow<Boolean> =
- MutableSharedFlow(replay = Int.MAX_VALUE),
-) : QSTileDataInteractor<T> {
+class FakeQSTileDataInteractor<T> : QSTileDataInteractor<T> {
+
+ private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = 1)
+ val dataSubscriptionCount
+ get() = dataFlow.subscriptionCount
+ private val availabilityFlow: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1)
+ val availabilitySubscriptionCount
+ get() = availabilityFlow.subscriptionCount
+
+ private val mutableTriggers = mutableListOf<DataUpdateTrigger>()
+ val triggers: List<DataUpdateTrigger> = mutableTriggers
private val mutableDataRequests = mutableListOf<DataRequest>()
val dataRequests: List<DataRequest> = mutableDataRequests
@@ -34,14 +39,17 @@
private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>()
val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests
- @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data)
+ suspend fun emitData(data: T): Unit = dataFlow.emit(data)
fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> {
mutableDataRequests.add(DataRequest(user))
- return triggers.flatMapLatest { dataFlow }
+ return triggers.flatMapLatest {
+ mutableTriggers.add(it)
+ dataFlow
+ }
}
override fun availability(user: UserHandle): Flow<Boolean> {
diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md
new file mode 100644
index 0000000..6adb6144
--- /dev/null
+++ b/ravenwood/README-ravenwood+mockito.md
@@ -0,0 +1,24 @@
+# Ravenwood and Mockito
+
+Last update: 2023-11-13
+
+- As of 2023-11-13, `external/mockito` is based on version 2.x.
+- Mockito didn't support static mocking before 3.4.0.
+ See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
+
+- Latest Mockito is 5.*. According to https://github.com/mockito/mockito:
+ `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.`
+
+- Mockito now supports Android natively.
+ See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1
+ - But it's unclear at this point to omakoto@ how the `mockito-android` module is built.
+
+- Potential plan:
+ - Ideal option:
+ - If we can update `external/mockito`, that'd be great, but it may not work because
+ Mockito has removed the deprecated APIs.
+ - Second option:
+ - Import the latest mockito as `external/mockito-new`, and require ravenwood
+ to use this one.
+ - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests.
+ - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
index 6dbff4c..4135022 100644
--- a/ravenwood/mockito/Android.bp
+++ b/ravenwood/mockito/Android.bp
@@ -36,3 +36,37 @@
],
auto_gen_config: true,
}
+
+android_test {
+ name: "RavenwoodMockitoTest_device",
+
+ srcs: [
+ "test/**/*.java",
+ ],
+ static_libs: [
+ "junit",
+ "truth",
+
+ "androidx.test.rules",
+
+ "ravenwood-junit",
+
+ "mockito-target-extended-minus-junit4",
+ ],
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+ jni_libs: [
+ // Required by mockito
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/ravenwood/mockito/AndroidManifest.xml b/ravenwood/mockito/AndroidManifest.xml
new file mode 100644
index 0000000..15f0a29
--- /dev/null
+++ b/ravenwood/mockito/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.ravenwood.mockitotest">
+
+ <application android:debuggable="true" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.ravenwood.mockitotest"
+ />
+</manifest>
diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/mockito/AndroidTest.xml
new file mode 100644
index 0000000..96bc275
--- /dev/null
+++ b/ravenwood/mockito/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Frameworks Services Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="RavenwoodMockitoTest_device.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="FrameworksMockingServicesTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.ravenwood.mockitotest" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/MockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/MockitoTest.java
deleted file mode 100644
index b175ae7..0000000
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/MockitoTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwood.mockito;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.Intent;
-
-import org.junit.Test;
-
-public class MockitoTest {
- @Test
- public void testMockJdkClass() {
- Process object = mock(Process.class);
-
- when(object.exitValue()).thenReturn(42);
-
- assertThat(object.exitValue()).isEqualTo(42);
- }
-
- /* It still doesn't work...
-STACKTRACE:
-org.mockito.exceptions.base.MockitoException:
-Mockito cannot mock this class: class android.content.Intent.
-
-Mockito can only mock non-private & non-final classes.
-If you're not sure why you're getting this error, please report to the mailing list.
-
-
-... But Intent public, non-final.
-
- */
- // @Test
- private void testMockAndroidClass1() {
- Intent object = mock(Intent.class);
-
- when(object.getAction()).thenReturn("ACTION_RAVENWOOD");
-
- assertThat(object.getAction()).isEqualTo("ACTION_RAVENWOOD");
- }
-
- @Test
- public void testMockAndroidClass2() {
- Context object = mock(Context.class);
-
- when(object.getPackageName()).thenReturn("android");
-
- assertThat(object.getPackageName()).isEqualTo("android");
- }
-}
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
new file mode 100644
index 0000000..36fa3dd
--- /dev/null
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwood.mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public class RavenwoodMockitoTest {
+ @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+
+// Use this to mock static methods, which isn't supported by mockito 2.
+// Mockito supports static mocking since 3.4.0:
+// See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
+
+// private MockitoSession mMockingSession;
+//
+// @Before
+// public void setUp() {
+// mMockingSession = mockitoSession()
+// .strictness(Strictness.LENIENT)
+// .mockStatic(RavenwoodMockitoTest.class)
+// .startMocking();
+// }
+//
+// @After
+// public void tearDown() {
+// if (mMockingSession != null) {
+// mMockingSession.finishMocking();
+// }
+// }
+
+ @Test
+ public void testMockJdkClass() {
+ Process object = mock(Process.class);
+
+ when(object.exitValue()).thenReturn(42);
+
+ assertThat(object.exitValue()).isEqualTo(42);
+ }
+
+ /*
+ - Intent can't be mocked because of the dependency to `org.xmlpull.v1.XmlPullParser`.
+ (The error says "Mockito can only mock non-private & non-final classes", but that's likely a
+ red-herring.)
+
+STACKTRACE:
+org.mockito.exceptions.base.MockitoException:
+Mockito cannot mock this class: class android.content.Intent.
+
+ :
+
+Underlying exception : java.lang.IllegalArgumentException: Could not create type
+ at com.android.ravenwood.mockito.RavenwoodMockitoTest.testMockAndroidClass1
+ at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+
+ :
+
+Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParser
+ at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
+ at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
+ at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
+ ... 54 more
+ */
+ @Test
+ @IgnoreUnderRavenwood
+ public void testMockAndroidClass1() {
+ Intent object = mock(Intent.class);
+
+ when(object.getAction()).thenReturn("ACTION_RAVENWOOD");
+
+ assertThat(object.getAction()).isEqualTo("ACTION_RAVENWOOD");
+ }
+
+ @Test
+ public void testMockAndroidClass2() {
+ Context object = mock(Context.class);
+
+ when(object.getPackageName()).thenReturn("android");
+
+ assertThat(object.getPackageName()).isEqualTo("android");
+ }
+}
diff --git a/services/proguard.flags b/services/proguard.flags
index 261bb7c..407505d 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -14,13 +14,20 @@
}
# APIs referenced by dependent JAR files and modules
--keep @interface android.annotation.SystemApi
+# TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro.
+-keep interface android.annotation.SystemApi
-keep @android.annotation.SystemApi class * {
public protected *;
}
-keepclasseswithmembers class * {
@android.annotation.SystemApi *;
}
+# Also ensure nested classes are kept. This is overly conservative, but handles
+# cases where such classes aren't explicitly marked @SystemApi.
+-if @android.annotation.SystemApi class *
+-keep public class <1>$** {
+ public protected *;
+}
# Derivatives of SystemService and other services created via reflection
-keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService {