Merge "Enable NotificationMemoryMonitor dumps in bug reports" into tm-qpr-dev
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 855366a..81c3e89 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -87,10 +87,12 @@
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StrictMode;
import android.os.Trace;
import android.os.UserHandle;
+import android.service.voice.VoiceInteractionSession;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -154,6 +156,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.app.WindowDecorActionBar;
@@ -1601,6 +1604,25 @@
return callbacks;
}
+ private void notifyVoiceInteractionManagerServiceActivityEvent(
+ @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
+
+ final IVoiceInteractionManagerService service =
+ IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ if (service == null) {
+ Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+ + "VoiceInteractionManagerService");
+ return;
+ }
+
+ try {
+ service.notifyActivityEventChanged(mToken, type);
+ } catch (RemoteException e) {
+ // Empty
+ }
+ }
+
/**
* Called when the activity is starting. This is where most initialization
* should go: calling {@link #setContentView(int)} to inflate the
@@ -1876,6 +1898,9 @@
mCalled = true;
notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START);
}
/**
@@ -2019,6 +2044,12 @@
final Window win = getWindow();
if (win != null) win.makeActive();
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+
+ // Because the test case "com.android.launcher3.jank.BinderTests#testPressHome" doesn't
+ // allow any binder call in onResume, we call this method in onPostResume.
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
+
mCalled = true;
}
@@ -2394,6 +2425,10 @@
getAutofillClientController().onActivityPaused();
notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE);
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE);
+
mCalled = true;
}
@@ -2623,6 +2658,9 @@
getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
mEnterAnimationComplete = false;
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
}
/**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f2ea060..c95a7de 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -731,10 +731,9 @@
*/
public interface VoiceInteractionManagerProvider {
/**
- * Notifies the service when a high-level activity event has been changed, for example,
- * an activity was resumed or stopped.
+ * Notifies the service when an activity is destroyed.
*/
- void notifyActivityEventChanged();
+ void notifyActivityDestroyed(IBinder activityToken);
}
/**
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index a432b8d..fd94969 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -42,12 +42,15 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.os.BackgroundThread;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -63,6 +66,7 @@
@RequiresFeature(PackageManager.FEATURE_APP_WIDGETS)
public class AppWidgetManager {
+
/**
* Activity action to launch from your {@link AppWidgetHost} activity when you want to
* pick an AppWidget to display. The AppWidget picker activity will be launched.
@@ -332,6 +336,17 @@
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
/**
+ * A combination broadcast of APPWIDGET_ENABLED and APPWIDGET_UPDATE.
+ * Sent during boot time and when the host is binding the widget for the very first time
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_APPWIDGET_ENABLE_AND_UPDATE = "android.appwidget.action"
+ + ".APPWIDGET_ENABLE_AND_UPDATE";
+
+ /**
* Sent when the custom extras for an AppWidget change.
*
* <p class="note">This is a protected intent that can only be sent
@@ -456,6 +471,8 @@
public static final String ACTION_APPWIDGET_HOST_RESTORED
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
+ private static final String TAG = "AppWidgetManager";
+
/**
* An intent extra that contains multiple appWidgetIds. These are id values as
* they were provided to the application during a recent restore from backup. It is
@@ -511,6 +528,26 @@
mPackageName = context.getOpPackageName();
mService = service;
mDisplayMetrics = context.getResources().getDisplayMetrics();
+ if (mService == null) {
+ return;
+ }
+ BackgroundThread.getExecutor().execute(() -> {
+ try {
+ mService.notifyProviderInheritance(getInstalledProvidersForPackage(mPackageName,
+ null)
+ .stream().filter(Objects::nonNull)
+ .map(info -> info.provider).filter(p -> {
+ try {
+ Class clazz = Class.forName(p.getClassName());
+ return AppWidgetProvider.class.isAssignableFrom(clazz);
+ } catch (Exception e) {
+ return false;
+ }
+ }).toArray(ComponentName[]::new));
+ } catch (Exception e) {
+ Log.e(TAG, "Nofity service of inheritance info", e);
+ }
+ });
}
/**
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index a5d2198..3344ebc 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -58,7 +58,12 @@
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
- if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ if (AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE.equals(action)) {
+ this.onReceive(context, new Intent(intent)
+ .setAction(AppWidgetManager.ACTION_APPWIDGET_ENABLED));
+ this.onReceive(context, new Intent(intent)
+ .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
+ } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 0fd164d..085bfca 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -918,7 +918,6 @@
}
}
-
/**
* Forwards BiometricStateListener to FingerprintService
* @param listener new BiometricStateListener being added
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c9fd129..a955dbb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9793,6 +9793,13 @@
"fingerprint_side_fps_auth_downtime";
/**
+ * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * @hide
+ */
+ public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+ "sfps_require_screen_on_to_auth_enabled";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index df727e9..48f732e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -19,6 +19,7 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -71,6 +72,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
@@ -143,6 +146,25 @@
*/
public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_START = 1;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_RESUME = 2;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE = 3;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_STOP = 4;
+
+ /** @hide */
+ @IntDef(prefix = { "VOICE_INTERACTION_ACTIVITY_EVENT_" }, value = {
+ VOICE_INTERACTION_ACTIVITY_EVENT_START,
+ VOICE_INTERACTION_ACTIVITY_EVENT_RESUME,
+ VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE,
+ VOICE_INTERACTION_ACTIVITY_EVENT_STOP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VoiceInteractionActivityEventType{}
+
final Context mContext;
final HandlerCaller mHandlerCaller;
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 681693b..bd51f12 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -18,6 +18,8 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Bundle;
@@ -25,18 +27,17 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.SharedMemory;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VisibleActivityInfo;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.internal.app.IVoiceInteractionSessionShowCallback;
-import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
-import android.hardware.soundtrigger.KeyphraseMetadata;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.service.voice.IVoiceInteractionService;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import com.android.internal.app.IVoiceInteractor;
interface IVoiceInteractionManagerService {
void showSession(in Bundle sessionArgs, int flags);
@@ -289,4 +290,14 @@
* Notifies when the session window is shown or hidden.
*/
void setSessionWindowVisible(in IBinder token, boolean visible);
+
+ /**
+ * Notifies when the Activity lifecycle event changed.
+ *
+ * @param activityToken The token of activity.
+ * @param type The type of lifecycle event of the activity lifecycle.
+ */
+ oneway void notifyActivityEventChanged(
+ in IBinder activityToken,
+ int type);
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6c689ff..44997b4 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -578,6 +578,11 @@
*/
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
+ /**
+ * (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE
+ */
+ public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f7467b5..7787b7c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -142,6 +142,7 @@
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE" />
<protected-broadcast android:name="android.os.action.SETTING_RESTORED" />
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 988303e..4b27bf2 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -31,5 +31,4 @@
lockscreen, setting this to true should come with customized drawables. -->
<bool name="use_lock_pattern_drawable">false</bool>
<bool name="resolver_landscape_phone">true</bool>
- <bool name="system_server_plays_face_haptics">true</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index aa9a949..bd8a429 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2491,8 +2491,10 @@
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
<bool name="config_dismissDreamOnActivityStart">false</bool>
- <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
- <string name="config_loggable_dream_prefix" translatable="false"></string>
+ <!-- The prefixes of dream component names that are loggable.
+ Matched against ComponentName#flattenToString() for dream components.
+ If empty, logs "other" for all. -->
+ <string-array name="config_loggable_dream_prefixes"></string-array>
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
@@ -4967,6 +4969,10 @@
<!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
<bool name="config_faceAuthDismissesKeyguard">true</bool>
+ <!-- Default value for whether a SFPS device is required to be interactive for fingerprint auth
+ to unlock the device. -->
+ <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7a7b43a..44d8468 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2245,7 +2245,7 @@
<java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
<java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
<java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
- <java-symbol type="string" name="config_loggable_dream_prefix" />
+ <java-symbol type="array" name="config_loggable_dream_prefixes" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
<java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2723,6 +2723,7 @@
<java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
<java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
<java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+ <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
<!-- Face config -->
<java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
@@ -4861,6 +4862,4 @@
<java-symbol type="id" name="language_picker_header" />
<java-symbol type="dimen" name="status_bar_height_default" />
-
- <java-symbol type="bool" name="system_server_plays_face_haptics" />
</resources>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e4..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@
@Override
public void onActivityPreCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
@@ -1934,6 +1949,11 @@
@Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1969,7 +1989,11 @@
if (who instanceof Activity) {
// We will check if the new activity should be split with the activity that launched
// it.
- launchingActivity = (Activity) who;
+ final Activity activity = (Activity) who;
+ // For Activity that is child of another Activity (ActivityGroup), treat the parent
+ // Activity as the launching one because it's window will just be a child of the
+ // parent Activity window.
+ launchingActivity = activity.isChild() ? activity.getParent() : activity;
if (isInPictureInPicture(launchingActivity)) {
// We don't embed activity when it is in PIP.
return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 7960323..362f1fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
- if (curSecondaryContainer != null
+ if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
&& (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
// Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
// the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
new file mode 100644
index 0000000..7237d2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-modules splitscreen owner
+chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 616d447..d28a9f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -612,9 +612,24 @@
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
+ int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChanged(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
+ int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+ if (!mEnablePipKeepClearAlgorithm) {
+ int pipTop = mPipBoundsState.getBounds().top;
+ int diff = newMaxMovementBound - oldMaxMovementBound;
+ if (diff < 0 && pipTop > newMaxMovementBound) {
+ // bottom inset has increased, move PiP up if it is too low
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
+ newMaxMovementBound - pipTop);
+ }
+ if (diff > 0 && oldMaxMovementBound == pipTop) {
+ // bottom inset has decreased, move PiP down if it was by the edge
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
+ }
+ }
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 975d4bb..a9a97be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -427,7 +427,7 @@
// If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
// occluded by the IME or shelf.
if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting()) {
+ if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
} else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index f4efc37..1c28c3d 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -6,3 +6,4 @@
lbill@google.com
madym@google.com
hwwang@google.com
+chenghsiuchang@google.com
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 453a713..8946516 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index a39735f..cbf7953 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 006b260..9add32c 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dream_overlay_status_bar"
+ android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="@dimen/dream_overlay_status_bar_height"
android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
diff --git a/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
new file mode 100644
index 0000000..aab914f
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+ such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+ to wide devices that are shorter in height, like foldables. -->
+<resources>
+ <!-- Space between status view and notification shelf -->
+ <dimen name="keyguard_status_view_bottom_margin">35dp</dimen>
+ <dimen name="keyguard_clock_top_margin">40dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 347cf29..d9df337 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,8 +17,6 @@
<resources>
<dimen name="notification_panel_margin_horizontal">48dp</dimen>
<dimen name="status_view_margin_horizontal">62dp</dimen>
- <dimen name="keyguard_clock_top_margin">40dp</dimen>
- <dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
<!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
new file mode 100644
index 0000000..97ead01
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+ such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+ to wide devices that are shorter in height, like foldables. -->
+<resources>
+ <!-- Space between status view and notification shelf -->
+ <dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
+ <dimen name="keyguard_clock_top_margin">80dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 3d8da8a..17f82b5 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,8 +21,6 @@
for different hardware and product builds. -->
<resources>
<dimen name="status_view_margin_horizontal">124dp</dimen>
- <dimen name="keyguard_clock_top_margin">80dp</dimen>
- <dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">200dp</dimen>
<dimen name="large_screen_shade_header_left_padding">24dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 93982cb..ce9829b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,6 +743,17 @@
<integer name="complicationRestoreMs">1000</integer>
+ <!-- Duration in milliseconds of the dream in un-blur animation. -->
+ <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
+ <!-- Delay in milliseconds of the dream in un-blur animation. -->
+ <integer name="config_dreamOverlayInBlurDelayMs">133</integer>
+ <!-- Duration in milliseconds of the dream in complications fade-in animation. -->
+ <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer>
+ <!-- Delay in milliseconds of the dream in top complications fade-in animation. -->
+ <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer>
+ <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. -->
+ <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer>
+
<!-- Icons that don't show in a collapsed non-keyguard statusbar -->
<string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
<item>@*android:string/status_bar_volume</item>
@@ -783,4 +794,8 @@
<item>@color/dream_overlay_aqi_very_unhealthy</item>
<item>@color/dream_overlay_aqi_hazardous</item>
</integer-array>
+
+ <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
+ is available. If false, UI will never show regardless of tethering availability" -->
+ <bool name="config_show_wifi_tethering">true</bool>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
new file mode 100644
index 0000000..2dc7a28
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.shared.keyguard.shared.model
+
+/**
+ * Collection of all supported "slots", placements where keyguard quick affordances can appear on
+ * the lock screen.
+ */
+object KeyguardQuickAffordanceSlots {
+ const val SLOT_ID_BOTTOM_START = "bottom_start"
+ const val SLOT_ID_BOTTOM_END = "bottom_end"
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index e743ec8..bfbe88c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,6 +20,7 @@
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import com.android.systemui.shared.recents.ISystemUiProxy;
oneway interface IOverviewProxy {
@@ -44,12 +45,6 @@
void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8;
/**
- * Sent when there was an action on one of the onboarding tips view.
- * TODO: Move this implementation to SystemUI completely
- */
- void onTip(int actionType, int viewType) = 10;
-
- /**
* Sent when device assistant changes its default assistant whether it is available or not.
*/
void onAssistantAvailable(boolean available) = 13;
@@ -60,13 +55,6 @@
void onAssistantVisibilityChanged(float visibility) = 14;
/**
- * Sent when back is triggered.
- * TODO: Move this implementation to SystemUI completely
- */
- void onBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) = 15;
-
- /**
* Sent when some system ui state changes.
*/
void onSystemUiStateChanged(int stateFlags) = 16;
@@ -115,4 +103,9 @@
* Sent when split keyboard shortcut is triggered to enter stage split.
*/
void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+
+ /**
+ * Sent when the surface for navigation bar is created or changed
+ */
+ void onNavigationBarSurface(in SurfaceControl surface) = 26;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2b2b05ce..b99b72b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -106,9 +106,6 @@
/** Sets home rotation enabled. */
void setHomeRotationEnabled(boolean enabled) = 45;
- /** Notifies that a swipe-up gesture has started */
- oneway void notifySwipeUpGestureStarted() = 46;
-
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index d48d7ff..c9b8712 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -228,7 +228,7 @@
listenForDozing(this)
if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
listenForDozeAmountTransition(this)
- listenForGoneToAodTransition(this)
+ listenForAnyStateToAodTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -286,10 +286,10 @@
* dozing.
*/
@VisibleForTesting
- internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+ internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.goneToAodTransition.filter {
- it.transitionState == TransitionState.STARTED
+ keyguardTransitionInteractor.anyStateToAodTransition.filter {
+ it.transitionState == TransitionState.FINISHED
}.collect {
dozeAmount = 1f
clock?.animations?.doze(dozeAmount)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8..a0206f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@
val keyguardOccluded: Boolean,
val occludingAppRequestingFp: Boolean,
val primaryUser: Boolean,
+ val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c756a17..2bb3a5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -106,8 +106,9 @@
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
- @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
+ @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
public @interface Mode {}
+ static final int MODE_UNINITIALIZED = -1;
static final int MODE_DEFAULT = 0;
static final int MODE_ONE_HANDED = 1;
static final int MODE_USER_SWITCHER = 2;
@@ -154,7 +155,11 @@
private boolean mDisappearAnimRunning;
private SwipeListener mSwipeListener;
private ViewMode mViewMode = new DefaultViewMode();
- private @Mode int mCurrentMode = MODE_DEFAULT;
+ /*
+ * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
+ * yet been called on it. This will happen when the ViewController is initialized.
+ */
+ private @Mode int mCurrentMode = MODE_UNINITIALIZED;
private int mWidth = -1;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -347,6 +352,8 @@
private String modeToString(@Mode int mode) {
switch (mode) {
+ case MODE_UNINITIALIZED:
+ return "Uninitialized";
case MODE_DEFAULT:
return "Default";
case MODE_ONE_HANDED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 79a01b9..fbb114c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -318,6 +318,7 @@
@Override
public void onInit() {
mSecurityViewFlipperController.init();
+ configureMode();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bad75e8..558869c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -151,6 +151,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Lists;
@@ -337,17 +338,20 @@
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
+ private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -396,6 +400,7 @@
protected Handler getHandler() {
return mHandler;
}
+
private final Handler mHandler;
private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -720,6 +725,7 @@
/**
* Request to listen for face authentication when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request face auth listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFace()}
@@ -732,6 +738,7 @@
/**
* Request to listen for fingerprint when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request fingerprint listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)}
@@ -1946,6 +1953,7 @@
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
+ SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -1988,6 +1996,7 @@
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
+ mSecureSettings = secureSettings;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2229,9 +2238,35 @@
Settings.System.TIME_12_24)));
}
};
+
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+ updateSfpsRequireScreenOnToAuthPref();
+ mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSfpsRequireScreenOnToAuthPref();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ mSecureSettings.getUriFor(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+ false,
+ mSfpsRequireScreenOnToAuthPrefObserver,
+ getCurrentUser());
+ }
+
+ protected void updateSfpsRequireScreenOnToAuthPref() {
+ final int defaultSfpsRequireScreenOnToAuthValue =
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+ mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ defaultSfpsRequireScreenOnToAuthValue,
+ getCurrentUser()) != 0;
}
private void initializeSimState() {
@@ -2276,6 +2311,22 @@
}
/**
+ * @return true if there's at least one sfps enrollment for the current user.
+ */
+ public boolean isSfpsEnrolled() {
+ return mAuthController.isSfpsEnrolled(getCurrentUser());
+ }
+
+ /**
+ * @return true if sfps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isSfpsSupported() {
+ return mAuthController.getSfpsProps() != null
+ && !mAuthController.getSfpsProps().isEmpty();
+ }
+
+ /**
* @return true if there's at least one face enrolled
*/
public boolean isFaceEnrolled() {
@@ -2598,13 +2649,21 @@
!(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
- && userDoesNotHaveTrust);
+ && !isEncryptedOrLockdownForUser
+ && userDoesNotHaveTrust);
- final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+ boolean shouldListenSideFpsState = true;
+ if (isSfpsSupported() && isSfpsEnrolled()) {
+ shouldListenSideFpsState =
+ mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+ }
+
+ boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+ && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenSideFpsState;
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
@@ -2626,6 +2685,7 @@
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
+ shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
@@ -3727,6 +3787,11 @@
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
+ if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mSfpsRequireScreenOnToAuthPrefObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3799,6 +3864,11 @@
pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+ } else if (isSfpsSupported()) {
+ pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
+ pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
+ pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
+ + mSfpsRequireScreenOnToAuthPrefEnabled);
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 37da2c7..0a2d8ec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -762,6 +762,12 @@
}
mContainerState = STATE_ANIMATING_OUT;
+ // Request hiding soft-keyboard before animating away credential UI, in case IME insets
+ // animation get delayed by dismissing animation.
+ if (isAttachedToWindow() && getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+
if (sendReason) {
mPendingCallbackReason = reason;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c015a21..ff18eee 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -78,6 +78,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
@@ -150,6 +151,7 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+ @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -159,6 +161,19 @@
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
+
+ private final VibratorHelper mVibratorHelper;
+
+ private void vibrateSuccess(int modality) {
+ mVibratorHelper.vibrateAuthSuccess(
+ getClass().getSimpleName() + ", modality = " + modality + "BP::success");
+ }
+
+ private void vibrateError(int modality) {
+ mVibratorHelper.vibrateAuthError(
+ getClass().getSimpleName() + ", modality = " + modality + "BP::error");
+ }
+
@VisibleForTesting
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
@@ -325,6 +340,16 @@
}
}
}
+
+ if (mSidefpsProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+ } else {
+ for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+ if (prop.sensorId == sensorId) {
+ mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged();
}
@@ -660,7 +685,8 @@
@NonNull StatusBarStateController statusBarStateController,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
- @Background DelayableExecutor bgExecutor) {
+ @Background DelayableExecutor bgExecutor,
+ @NonNull VibratorHelper vibrator) {
mContext = context;
mExecution = execution;
mUserManager = userManager;
@@ -677,6 +703,8 @@
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mSfpsEnrolledForUser = new SparseBooleanArray();
+ mVibratorHelper = vibrator;
mOrientationListener = new BiometricDisplayListener(
context,
@@ -866,6 +894,8 @@
public void onBiometricAuthenticated(@Modality int modality) {
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
+ vibrateSuccess(modality);
+
if (mCurrentDialog != null) {
mCurrentDialog.onAuthenticationSucceeded(modality);
} else {
@@ -889,6 +919,11 @@
return mUdfpsProps;
}
+ @Nullable
+ public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+ return mSidefpsProps;
+ }
+
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
@@ -913,6 +948,8 @@
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
}
+ vibrateError(modality);
+
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
@@ -1013,6 +1050,17 @@
return mUdfpsEnrolledForUser.get(userId);
}
+ /**
+ * Whether the passed userId has enrolled SFPS.
+ */
+ public boolean isSfpsEnrolled(int userId) {
+ if (mSidefpsController == null) {
+ return false;
+ }
+
+ return mSfpsEnrolledForUser.get(userId);
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 96fe65f..65fcd76 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -60,7 +60,9 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -119,6 +121,7 @@
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final VibratorHelper mVibrator;
+ @NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -130,6 +133,7 @@
@NonNull private final LatencyTracker mLatencyTracker;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ @NonNull private final BouncerInteractor mBouncerInteractor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -212,7 +216,8 @@
mUnlockedScreenOffAnimationController,
mUdfpsDisplayMode, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator)));
+ fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
+ mBouncerInteractor)));
}
@Override
@@ -590,6 +595,7 @@
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull FeatureFlags featureFlags,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -608,7 +614,8 @@
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
- @BiometricsBackground Executor biometricsExecutor) {
+ @BiometricsBackground Executor biometricsExecutor,
+ @NonNull BouncerInteractor bouncerInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -638,6 +645,8 @@
mActivityLaunchAnimator = activityLaunchAnimator;
mAlternateTouchProvider = alternateTouchProvider.orElse(null);
mBiometricExecutor = biometricsExecutor;
+ mFeatureFlags = featureFlags;
+ mBouncerInteractor = bouncerInteractor;
mOrientationListener = new BiometricDisplayListener(
context,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7d01096..d70861a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -48,6 +48,8 @@
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -70,29 +72,31 @@
*/
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
- private val context: Context,
- fingerprintManager: FingerprintManager,
- private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
- private val accessibilityManager: AccessibilityManager,
- private val statusBarStateController: StatusBarStateController,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager,
- private val transitionController: LockscreenShadeTransitionController,
- private val configurationController: ConfigurationController,
- private val systemClock: SystemClock,
- private val keyguardStateController: KeyguardStateController,
- private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
- private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
- val requestId: Long,
- @ShowReason val requestReason: Int,
- private val controllerCallback: IUdfpsOverlayControllerCallback,
- private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
+ private val context: Context,
+ fingerprintManager: FingerprintManager,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val accessibilityManager: AccessibilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dialogManager: SystemUIDialogManager,
+ private val dumpManager: DumpManager,
+ private val transitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ val requestId: Long,
+ @ShowReason val requestReason: Int,
+ private val controllerCallback: IUdfpsOverlayControllerCallback,
+ private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -246,7 +250,9 @@
unlockedScreenOffAnimationController,
dialogManager,
controller,
- activityLaunchAnimator
+ activityLaunchAnimator,
+ featureFlags,
+ bouncerInteractor
)
}
REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
deleted file mode 100644
index 4d7f89d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.util.MathUtils;
-import android.view.MotionEvent;
-
-import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-
-import java.io.PrintWriter;
-
-/**
- * Class that coordinates non-HBM animations during keyguard authentication.
- */
-public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
- public static final String TAG = "UdfpsKeyguardViewCtrl";
- @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
- @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
- @NonNull private final ConfigurationController mConfigurationController;
- @NonNull private final SystemClock mSystemClock;
- @NonNull private final KeyguardStateController mKeyguardStateController;
- @NonNull private final UdfpsController mUdfpsController;
- @NonNull private final UnlockedScreenOffAnimationController
- mUnlockedScreenOffAnimationController;
- @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
- private final ValueAnimator mUnlockedScreenOffDozeAnimator = ValueAnimator.ofFloat(0f, 1f);
-
- private boolean mShowingUdfpsBouncer;
- private boolean mUdfpsRequested;
- private float mQsExpansion;
- private boolean mFaceDetectRunning;
- private int mStatusBarState;
- private float mTransitionToFullShadeProgress;
- private float mLastDozeAmount;
- private long mLastUdfpsBouncerShowTime = -1;
- private float mPanelExpansionFraction;
- private boolean mLaunchTransitionFadingAway;
- private boolean mIsLaunchingActivity;
- private float mActivityLaunchProgress;
-
- /**
- * hidden amount of pin/pattern/password bouncer
- * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
- * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f)
- */
- private float mInputBouncerHiddenAmount;
- private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible
-
- protected UdfpsKeyguardViewController(
- @NonNull UdfpsKeyguardView view,
- @NonNull StatusBarStateController statusBarStateController,
- @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
- @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DumpManager dumpManager,
- @NonNull LockscreenShadeTransitionController transitionController,
- @NonNull ConfigurationController configurationController,
- @NonNull SystemClock systemClock,
- @NonNull KeyguardStateController keyguardStateController,
- @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- @NonNull SystemUIDialogManager systemUIDialogManager,
- @NonNull UdfpsController udfpsController,
- @NonNull ActivityLaunchAnimator activityLaunchAnimator) {
- super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
- dumpManager);
- mKeyguardViewManager = statusBarKeyguardViewManager;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLockScreenShadeTransitionController = transitionController;
- mConfigurationController = configurationController;
- mSystemClock = systemClock;
- mKeyguardStateController = keyguardStateController;
- mUdfpsController = udfpsController;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- mActivityLaunchAnimator = activityLaunchAnimator;
-
- mUnlockedScreenOffDozeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mUnlockedScreenOffDozeAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mUnlockedScreenOffDozeAnimator.addUpdateListener(
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mView.onDozeAmountChanged(
- animation.getAnimatedFraction(),
- (float) animation.getAnimatedValue(),
- UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
- }
- });
- }
-
- @Override
- @NonNull protected String getTag() {
- return "UdfpsKeyguardViewController";
- }
-
- @Override
- public void onInit() {
- super.onInit();
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- }
-
- @Override
- protected void onViewAttached() {
- super.onViewAttached();
- final float dozeAmount = getStatusBarStateController().getDozeAmount();
- mLastDozeAmount = dozeAmount;
- mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
- getStatusBarStateController().addCallback(mStateListener);
-
- mUdfpsRequested = false;
-
- mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
- mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
- mStatusBarState = getStatusBarStateController().getState();
- mQsExpansion = mKeyguardViewManager.getQsExpansion();
- updateGenericBouncerVisibility();
- mConfigurationController.addCallback(mConfigurationListener);
- getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
- updateScaleFactor();
- mView.updatePadding();
- updateAlpha();
- updatePauseAuth();
-
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
- mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- protected void onViewDetached() {
- super.onViewDetached();
- mFaceDetectRunning = false;
-
- mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
- getStatusBarStateController().removeCallback(mStateListener);
- mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- mConfigurationController.removeCallback(mConfigurationListener);
- getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
- if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
- }
- mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- super.dump(pw, args);
- pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer);
- pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
- pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState));
- pw.println("mTransitionToFullShadeProgress=" + mTransitionToFullShadeProgress);
- pw.println("mQsExpansion=" + mQsExpansion);
- pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing);
- pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
- pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction);
- pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
- pw.println("mUdfpsRequested=" + mUdfpsRequested);
- pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
- pw.println("mLastDozeAmount=" + mLastDozeAmount);
-
- mView.dump(pw);
- }
-
- /**
- * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
- * @return whether the udfpsBouncer has been newly shown or hidden
- */
- private boolean showUdfpsBouncer(boolean show) {
- if (mShowingUdfpsBouncer == show) {
- return false;
- }
-
- boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
- mShowingUdfpsBouncer = show;
- if (mShowingUdfpsBouncer) {
- mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
- }
- if (mShowingUdfpsBouncer) {
- if (udfpsAffordanceWasNotShowing) {
- mView.animateInUdfpsBouncer(null);
- }
-
- if (mKeyguardStateController.isOccluded()) {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
- }
-
- mView.announceForAccessibility(mView.getContext().getString(
- R.string.accessibility_fingerprint_bouncer));
- } else {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- }
-
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- return true;
- }
-
- /**
- * Returns true if the fingerprint manager is running but we want to temporarily pause
- * authentication. On the keyguard, we may want to show udfps when the shade
- * is expanded, so this can be overridden with the showBouncer method.
- */
- public boolean shouldPauseAuth() {
- if (mShowingUdfpsBouncer) {
- return false;
- }
-
- if (mUdfpsRequested && !getNotificationShadeVisible()
- && (!mIsGenericBouncerShowing
- || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)
- && mKeyguardStateController.isShowing()) {
- return false;
- }
-
- if (mLaunchTransitionFadingAway) {
- return true;
- }
-
- // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
- // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
- // delayed. However, we still animate in the UDFPS affordance with the
- // mUnlockedScreenOffDozeAnimator.
- if (mStatusBarState != KEYGUARD && mLastDozeAmount == 0f) {
- return true;
- }
-
- if (mInputBouncerHiddenAmount < .5f) {
- return true;
- }
-
- if (mView.getUnpausedAlpha() < (255 * .1)) {
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean listenForTouchesOutsideView() {
- return true;
- }
-
- @Override
- public void onTouchOutsideView() {
- maybeShowInputBouncer();
- }
-
- /**
- * If we were previously showing the udfps bouncer, hide it and instead show the regular
- * (pin/pattern/password) bouncer.
- *
- * Does nothing if we weren't previously showing the UDFPS bouncer.
- */
- private void maybeShowInputBouncer() {
- if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
- mKeyguardViewManager.showBouncer(true);
- }
- }
-
- /**
- * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
- * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
- * bouncer.
- */
- private boolean hasUdfpsBouncerShownWithMinTime() {
- return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
- }
-
- /**
- * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
- * transitioning yet, while 1.0f means we've fully dragged down.
- *
- * For example, start swiping down to expand the notification shade from the empty space in
- * the middle of the lock screen.
- */
- public void setTransitionToFullShadeProgress(float progress) {
- mTransitionToFullShadeProgress = progress;
- updateAlpha();
- }
-
- /**
- * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's
- * alpha is based on the doze amount.
- */
- @Override
- public void updateAlpha() {
- // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
- // then the keyguard is occluded by some application - so instead use the input bouncer
- // hidden amount to determine the fade.
- float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mPanelExpansionFraction;
-
- int alpha = mShowingUdfpsBouncer ? 255
- : (int) MathUtils.constrain(
- MathUtils.map(.5f, .9f, 0f, 255f, expansion),
- 0f, 255f);
-
- if (!mShowingUdfpsBouncer) {
- // swipe from top of the lockscreen to expand full QS:
- alpha *= (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(mQsExpansion));
-
- // swipe from the middle (empty space) of lockscreen to expand the notification shade:
- alpha *= (1.0f - mTransitionToFullShadeProgress);
-
- // Fade out the icon if we are animating an activity launch over the lockscreen and the
- // activity didn't request the UDFPS.
- if (mIsLaunchingActivity && !mUdfpsRequested) {
- alpha *= (1.0f - mActivityLaunchProgress);
- }
-
- // Fade out alpha when a dialog is shown
- // Fade in alpha when a dialog is hidden
- alpha *= mView.getDialogSuggestedAlpha();
- }
- mView.setUnpausedAlpha(alpha);
- }
-
- /**
- * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the
- * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not.
- */
- private void updateGenericBouncerVisibility() {
- mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer
- final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth();
- if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) {
- mInputBouncerHiddenAmount = 1f;
- } else if (mIsGenericBouncerShowing) {
- // input bouncer is fully showing
- mInputBouncerHiddenAmount = 0f;
- }
- }
-
- /**
- * Update the scale factor based on the device's resolution.
- */
- private void updateScaleFactor() {
- if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) {
- mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor());
- }
- }
-
- private final StatusBarStateController.StateListener mStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- if (mLastDozeAmount < linear) {
- showUdfpsBouncer(false);
- }
- mUnlockedScreenOffDozeAnimator.cancel();
- final boolean animatingFromUnlockedScreenOff =
- mUnlockedScreenOffAnimationController.isAnimationPlaying();
- if (animatingFromUnlockedScreenOff && linear != 0f) {
- // we manually animate the fade in of the UDFPS icon since the unlocked
- // screen off animation prevents the doze amounts to be incrementally eased in
- mUnlockedScreenOffDozeAnimator.start();
- } else {
- mView.onDozeAmountChanged(linear, eased,
- UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
- }
-
- mLastDozeAmount = linear;
- updatePauseAuth();
- }
-
- @Override
- public void onStateChanged(int statusBarState) {
- mStatusBarState = statusBarState;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
- new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
- @Override
- public boolean showAlternateAuthBouncer() {
- return showUdfpsBouncer(true);
- }
-
- @Override
- public boolean hideAlternateAuthBouncer() {
- return showUdfpsBouncer(false);
- }
-
- @Override
- public boolean isShowingAlternateAuthBouncer() {
- return mShowingUdfpsBouncer;
- }
-
- @Override
- public void requestUdfps(boolean request, int color) {
- mUdfpsRequested = request;
- mView.requestUdfps(request, color);
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean isAnimating() {
- return false;
- }
-
- /**
- * Set the amount qs is expanded. Forxample, swipe down from the top of the
- * lock screen to start the full QS expansion.
- */
- @Override
- public void setQsExpansion(float qsExpansion) {
- mQsExpansion = qsExpansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean onTouch(MotionEvent event) {
- if (mTransitionToFullShadeProgress != 0) {
- return false;
- }
- return mUdfpsController.onTouch(event);
- }
-
- @Override
- public void setBouncerExpansionChanged(float expansion) {
- mInputBouncerHiddenAmount = expansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- /**
- * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer
- * visibility changes.
- */
- @Override
- public void onBouncerVisibilityChanged() {
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.println(getTag());
- }
- };
-
- private final ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onUiModeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onThemeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- updateScaleFactor();
- mView.updatePadding();
- mView.updateColor();
- }
- };
-
- private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
- @Override
- public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
- float fraction = event.getFraction();
- mPanelExpansionFraction =
- mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
- .aboutToShowBouncerProgress(fraction) : fraction;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onLaunchTransitionFadingAwayChanged() {
- mLaunchTransitionFadingAway =
- mKeyguardStateController.isLaunchTransitionFadingAway();
- updatePauseAuth();
- }
- };
-
- private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
- new ActivityLaunchAnimator.Listener() {
- @Override
- public void onLaunchAnimationStart() {
- mIsLaunchingActivity = true;
- mActivityLaunchProgress = 0f;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationEnd() {
- mIsLaunchingActivity = false;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationProgress(float linearProgress) {
- mActivityLaunchProgress = linearProgress;
- updateAlpha();
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..5bae2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.animation.ValueAnimator
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+open class UdfpsKeyguardViewController
+constructor(
+ private val view: UdfpsKeyguardView,
+ statusBarStateController: StatusBarStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val keyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ dumpManager: DumpManager,
+ private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ systemUIDialogManager: SystemUIDialogManager,
+ private val udfpsController: UdfpsController,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor
+) :
+ UdfpsAnimationViewController<UdfpsKeyguardView>(
+ view,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager
+ ) {
+ private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
+ private var showingUdfpsBouncer = false
+ private var udfpsRequested = false
+ private var qsExpansion = 0f
+ private var faceDetectRunning = false
+ private var statusBarState = 0
+ private var transitionToFullShadeProgress = 0f
+ private var lastDozeAmount = 0f
+ private var lastUdfpsBouncerShowTime: Long = -1
+ private var panelExpansionFraction = 0f
+ private var launchTransitionFadingAway = false
+ private var isLaunchingActivity = false
+ private var activityLaunchProgress = 0f
+ private val unlockedScreenOffDozeAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
+ interpolator = Interpolators.ALPHA_IN
+ addUpdateListener { animation ->
+ view.onDozeAmountChanged(
+ animation.animatedFraction,
+ animation.animatedValue as Float,
+ UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF
+ )
+ }
+ }
+ /**
+ * Hidden amount of input (pin/pattern/password) bouncer. This is used
+ * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only
+ * used for the non-modernBouncer.
+ */
+ private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN
+ private var inputBouncerExpansion = 0f // only used for modernBouncer
+
+ private val stateListener: StatusBarStateController.StateListener =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ if (lastDozeAmount < linear) {
+ showUdfpsBouncer(false)
+ }
+ unlockedScreenOffDozeAnimator.cancel()
+ val animatingFromUnlockedScreenOff =
+ unlockedScreenOffAnimationController.isAnimationPlaying()
+ if (animatingFromUnlockedScreenOff && linear != 0f) {
+ // we manually animate the fade in of the UDFPS icon since the unlocked
+ // screen off animation prevents the doze amounts to be incrementally eased in
+ unlockedScreenOffDozeAnimator.start()
+ } else {
+ view.onDozeAmountChanged(
+ linear,
+ eased,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+ )
+ }
+ lastDozeAmount = linear
+ updatePauseAuth()
+ }
+
+ override fun onStateChanged(statusBarState: Int) {
+ this@UdfpsKeyguardViewController.statusBarState = statusBarState
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val bouncerExpansionCallback: BouncerExpansionCallback =
+ object : BouncerExpansionCallback {
+ override fun onExpansionChanged(expansion: Float) {
+ inputBouncerHiddenAmount = expansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun onVisibilityChanged(isVisible: Boolean) {
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val configurationListener: ConfigurationController.ConfigurationListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ view.updateColor()
+ }
+
+ override fun onThemeChanged() {
+ view.updateColor()
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ updateScaleFactor()
+ view.updatePadding()
+ view.updateColor()
+ }
+ }
+
+ private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
+ panelExpansionFraction =
+ if (keyguardViewManager.isBouncerInTransit) {
+ aboutToShowBouncerProgress(fraction)
+ } else {
+ fraction
+ }
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+ object : KeyguardStateController.Callback {
+ override fun onLaunchTransitionFadingAwayChanged() {
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ updatePauseAuth()
+ }
+ }
+
+ private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
+ object : ActivityLaunchAnimator.Listener {
+ override fun onLaunchAnimationStart() {
+ isLaunchingActivity = true
+ activityLaunchProgress = 0f
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationEnd() {
+ isLaunchingActivity = false
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationProgress(linearProgress: Float) {
+ activityLaunchProgress = linearProgress
+ updateAlpha()
+ }
+ }
+
+ private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
+ object : KeyguardViewManagerCallback {
+ override fun onQSExpansionChanged(qsExpansion: Float) {
+ this@UdfpsKeyguardViewController.qsExpansion = qsExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ /**
+ * Forward touches to the UdfpsController. This allows the touch to start from outside
+ * the sensor area and then slide their finger into the sensor area.
+ */
+ override fun onTouch(event: MotionEvent) {
+ // Don't forward touches if the shade has already started expanding.
+ if (transitionToFullShadeProgress != 0f) {
+ return
+ }
+ udfpsController.onTouch(event)
+ }
+ }
+
+ private val alternateAuthInterceptor: AlternateAuthInterceptor =
+ object : AlternateAuthInterceptor {
+ override fun showAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(true)
+ }
+
+ override fun hideAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(false)
+ }
+
+ override fun isShowingAlternateAuthBouncer(): Boolean {
+ return showingUdfpsBouncer
+ }
+
+ override fun requestUdfps(request: Boolean, color: Int) {
+ udfpsRequested = request
+ view.requestUdfps(request, color)
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun dump(pw: PrintWriter) {
+ pw.println(tag)
+ }
+ }
+
+ override val tag: String
+ get() = TAG
+
+ override fun onInit() {
+ super.onInit()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ }
+
+ init {
+ if (isModernBouncerEnabled) {
+ view.repeatWhenAttached {
+ // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+ // can make the view not visible; and we still want to listen for events
+ // that may make the view visible again.
+ repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ return scope.launch {
+ bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+ inputBouncerExpansion = bouncerExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+ }
+
+ public override fun onViewAttached() {
+ super.onViewAttached()
+ val dozeAmount = statusBarStateController.dozeAmount
+ lastDozeAmount = dozeAmount
+ stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
+ statusBarStateController.addCallback(stateListener)
+ udfpsRequested = false
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ keyguardStateController.addCallback(keyguardStateControllerCallback)
+ statusBarState = statusBarStateController.state
+ qsExpansion = keyguardViewManager.qsExpansion
+ keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ val bouncer = keyguardViewManager.bouncer
+ bouncer?.expansion?.let {
+ bouncerExpansionCallback.onExpansionChanged(it)
+ bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ updateBouncerHiddenAmount()
+ }
+ configurationController.addCallback(configurationListener)
+ shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
+ updateScaleFactor()
+ view.updatePadding()
+ updateAlpha()
+ updatePauseAuth()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = this
+ activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ }
+
+ override fun onViewDetached() {
+ super.onViewDetached()
+ faceDetectRunning = false
+ keyguardStateController.removeCallback(keyguardStateControllerCallback)
+ statusBarStateController.removeCallback(stateListener)
+ keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ configurationController.removeCallback(configurationListener)
+ shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
+ if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) {
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = null
+ }
+ activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+ keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ super.dump(pw, args)
+ pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
+ pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
+ pw.println("faceDetectRunning=$faceDetectRunning")
+ pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
+ pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
+ pw.println("qsExpansion=$qsExpansion")
+ pw.println("panelExpansionFraction=$panelExpansionFraction")
+ pw.println("unpausedAlpha=" + view.unpausedAlpha)
+ pw.println("udfpsRequestedByApp=$udfpsRequested")
+ pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
+ pw.println("lastDozeAmount=$lastDozeAmount")
+ if (isModernBouncerEnabled) {
+ pw.println("inputBouncerExpansion=$inputBouncerExpansion")
+ } else {
+ pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
+ }
+ view.dump(pw)
+ }
+
+ /**
+ * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
+ * @return whether the udfpsBouncer has been newly shown or hidden
+ */
+ private fun showUdfpsBouncer(show: Boolean): Boolean {
+ if (showingUdfpsBouncer == show) {
+ return false
+ }
+ val udfpsAffordanceWasNotShowing = shouldPauseAuth()
+ showingUdfpsBouncer = show
+ if (showingUdfpsBouncer) {
+ lastUdfpsBouncerShowTime = systemClock.uptimeMillis()
+ }
+ if (showingUdfpsBouncer) {
+ if (udfpsAffordanceWasNotShowing) {
+ view.animateInUdfpsBouncer(null)
+ }
+ if (keyguardStateController.isOccluded) {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
+ }
+ view.announceForAccessibility(
+ view.context.getString(R.string.accessibility_fingerprint_bouncer)
+ )
+ } else {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ }
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ return true
+ }
+
+ /**
+ * Returns true if the fingerprint manager is running but we want to temporarily pause
+ * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
+ * this can be overridden with the showBouncer method.
+ */
+ override fun shouldPauseAuth(): Boolean {
+ if (showingUdfpsBouncer) {
+ return false
+ }
+ if (
+ udfpsRequested &&
+ !notificationShadeVisible &&
+ !isInputBouncerFullyVisible() &&
+ keyguardStateController.isShowing
+ ) {
+ return false
+ }
+ if (launchTransitionFadingAway) {
+ return true
+ }
+
+ // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
+ // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
+ // delayed. However, we still animate in the UDFPS affordance with the
+ // mUnlockedScreenOffDozeAnimator.
+ if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
+ return true
+ }
+ if (isBouncerExpansionGreaterThan(.5f)) {
+ return true
+ }
+ return view.unpausedAlpha < 255 * .1
+ }
+
+ fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion >= bouncerExpansionThreshold
+ } else {
+ inputBouncerHiddenAmount < bouncerExpansionThreshold
+ }
+ }
+
+ fun isInputBouncerFullyVisible(): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion == 1f
+ } else {
+ keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+ }
+ }
+
+ override fun listenForTouchesOutsideView(): Boolean {
+ return true
+ }
+
+ override fun onTouchOutsideView() {
+ maybeShowInputBouncer()
+ }
+
+ /**
+ * If we were previously showing the udfps bouncer, hide it and instead show the regular
+ * (pin/pattern/password) bouncer.
+ *
+ * Does nothing if we weren't previously showing the UDFPS bouncer.
+ */
+ private fun maybeShowInputBouncer() {
+ if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
+ keyguardViewManager.showBouncer(true)
+ }
+ }
+
+ /**
+ * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the
+ * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer.
+ */
+ private fun hasUdfpsBouncerShownWithMinTime(): Boolean {
+ return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200
+ }
+
+ /**
+ * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
+ * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
+ * to expand the notification shade from the empty space in the middle of the lock screen.
+ */
+ fun setTransitionToFullShadeProgress(progress: Float) {
+ transitionToFullShadeProgress = progress
+ updateAlpha()
+ }
+
+ /**
+ * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
+ * based on the doze amount.
+ */
+ override fun updateAlpha() {
+ // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
+ // then the keyguard is occluded by some application - so instead use the input bouncer
+ // hidden amount to determine the fade.
+ val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
+ var alpha: Int =
+ if (showingUdfpsBouncer) 255
+ else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
+ if (!showingUdfpsBouncer) {
+ // swipe from top of the lockscreen to expand full QS:
+ alpha =
+ (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
+ .toInt()
+
+ // swipe from the middle (empty space) of lockscreen to expand the notification shade:
+ alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
+
+ // Fade out the icon if we are animating an activity launch over the lockscreen and the
+ // activity didn't request the UDFPS.
+ if (isLaunchingActivity && !udfpsRequested) {
+ alpha = (alpha * (1.0f - activityLaunchProgress)).toInt()
+ }
+
+ // Fade out alpha when a dialog is shown
+ // Fade in alpha when a dialog is hidden
+ alpha = (alpha * view.dialogSuggestedAlpha).toInt()
+ }
+ view.unpausedAlpha = alpha
+ }
+
+ private fun getInputBouncerHiddenAmt(): Float {
+ return if (isModernBouncerEnabled) {
+ 1f - inputBouncerExpansion
+ } else {
+ inputBouncerHiddenAmount
+ }
+ }
+
+ /** Update the scale factor based on the device's resolution. */
+ private fun updateScaleFactor() {
+ udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
+ }
+
+ private fun updateBouncerHiddenAmount() {
+ if (isModernBouncerEnabled) {
+ return
+ }
+ val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
+ if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+ inputBouncerHiddenAmount = 1f
+ } else if (keyguardViewManager.isBouncerShowing) {
+ // input bouncer is fully showing
+ inputBouncerHiddenAmount = 0f
+ }
+ }
+
+ companion object {
+ const val TAG = "UdfpsKeyguardViewController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
new file mode 100644
index 0000000..d8dd6a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.statusbar.BlurUtils
+import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for dream overlay animations. */
+class DreamOverlayAnimationsController
+@Inject
+constructor(
+ private val mBlurUtils: BlurUtils,
+ private val mComplicationHostViewController: ComplicationHostViewController,
+ private val mStatusBarViewController: DreamOverlayStatusBarViewController,
+ private val mOverlayStateController: DreamOverlayStateController,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
+ private val mDreamInBlurAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+ private val mDreamInComplicationsAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInTopComplicationsAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInBottomComplicationsAnimDelay: Int
+) {
+
+ var mEntryAnimations: AnimatorSet? = null
+
+ /** Starts the dream content and dream overlay entry animations. */
+ fun startEntryAnimations(view: View) {
+ cancelRunningEntryAnimations()
+
+ mEntryAnimations = AnimatorSet()
+ mEntryAnimations?.apply {
+ playTogether(
+ buildDreamInBlurAnimator(view),
+ buildDreamInTopComplicationsAnimator(),
+ buildDreamInBottomComplicationsAnimator()
+ )
+ doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
+ start()
+ }
+ }
+
+ /** Cancels the dream content and dream overlay animations, if they're currently running. */
+ fun cancelRunningEntryAnimations() {
+ if (mEntryAnimations?.isRunning == true) {
+ mEntryAnimations?.cancel()
+ }
+ mEntryAnimations = null
+ }
+
+ private fun buildDreamInBlurAnimator(view: View): Animator {
+ return ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = mDreamInBlurAnimDuration.toLong()
+ startDelay = mDreamInBlurAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animator: ValueAnimator ->
+ mBlurUtils.applyBlur(
+ view.viewRootImpl,
+ mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
+ false /*opaque*/
+ )
+ }
+ }
+ }
+
+ private fun buildDreamInTopComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setTopElementsAlpha(va.animatedValue as Float)
+ }
+ }
+ }
+
+ private fun buildDreamInBottomComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setBottomElementsAlpha(va.animatedValue as Float)
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
+ }
+ }
+ )
+ }
+ }
+
+ /** Sets alpha of top complications and the status bar. */
+ private fun setTopElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ mStatusBarViewController.setAlpha(alpha)
+ }
+
+ /** Sets alpha of bottom complications. */
+ private fun setBottomElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ }
+
+ private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
+ if (alpha > 0 && view.visibility != View.VISIBLE) {
+ view.visibility = View.VISIBLE
+ }
+
+ view.alpha = alpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 733a80d..d0d0184 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -54,6 +54,8 @@
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final BlurUtils mBlurUtils;
+ private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
+ private final DreamOverlayStateController mStateController;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -134,12 +136,16 @@
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval,
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
- BouncerCallbackInteractor bouncerCallbackInteractor) {
+ BouncerCallbackInteractor bouncerCallbackInteractor,
+ DreamOverlayAnimationsController animationsController,
+ DreamOverlayStateController stateController) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mBlurUtils = blurUtils;
+ mDreamOverlayAnimationsController = animationsController;
+ mStateController = stateController;
mComplicationHostViewController = complicationHostViewController;
mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
@@ -172,6 +178,11 @@
bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+
+ // Start dream entry animations. Skip animations for low light clock.
+ if (!mStateController.isLowLightActive()) {
+ mDreamOverlayAnimationsController.startEntryAnimations(mView);
+ }
}
@Override
@@ -182,6 +193,8 @@
bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+
+ mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
}
View getContainerView() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d1b7368..6380fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -257,6 +257,7 @@
mStateController.setOverlayActive(false);
mStateController.setLowLightActive(false);
+ mStateController.setEntryAnimationsFinished(false);
mDreamOverlayContainerViewController = null;
mDreamOverlayTouchMonitor = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 72feaca..e80d0be 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -51,6 +51,7 @@
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
+ public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -202,6 +203,14 @@
return containsState(STATE_LOW_LIGHT_ACTIVE);
}
+ /**
+ * Returns whether the dream content and dream overlay entry animations are finished.
+ * @return {@code true} if animations are finished, {@code false} otherwise.
+ */
+ public boolean areEntryAnimationsFinished() {
+ return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -218,7 +227,7 @@
}
if (existingState != mState) {
- notifyCallbacks(callback -> callback.onStateChanged());
+ notifyCallbacks(Callback::onStateChanged);
}
}
@@ -239,6 +248,15 @@
}
/**
+ * Sets whether dream content and dream overlay entry animations are finished.
+ * @param finished {@code true} if entry animations are finished, {@code false} otherwise.
+ */
+ public void setEntryAnimationsFinished(boolean finished) {
+ modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE,
+ STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index bb1c430..d17fbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -16,10 +16,6 @@
package com.android.systemui.dreams;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-
import android.app.AlarmManager;
import android.app.StatusBarManager;
import android.content.res.Resources;
@@ -83,6 +79,9 @@
private boolean mIsAttached;
+ // Whether dream entry animations are finished.
+ private boolean mEntryAnimationsFinished = false;
+
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -109,7 +108,9 @@
new DreamOverlayStateController.Callback() {
@Override
public void onStateChanged() {
- updateLowLightState();
+ mEntryAnimationsFinished =
+ mDreamOverlayStateController.areEntryAnimationsFinished();
+ updateVisibility();
}
};
@@ -195,7 +196,6 @@
mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
- updateLowLightState();
mTouchInsetSession.addViewToTracking(mView);
}
@@ -216,6 +216,26 @@
mIsAttached = false;
}
+ /**
+ * Sets alpha of the dream overlay status bar.
+ *
+ * No-op if the dream overlay status bar should not be shown.
+ */
+ protected void setAlpha(float alpha) {
+ updateVisibility();
+
+ if (mView.getVisibility() != View.VISIBLE) {
+ return;
+ }
+
+ mView.setAlpha(alpha);
+ }
+
+ private boolean shouldShowStatusBar() {
+ return !mDreamOverlayStateController.isLowLightActive()
+ && !mStatusBarWindowStateController.windowIsShowing();
+ }
+
private void updateWifiUnavailableStatusIcon() {
final NetworkCapabilities capabilities =
mConnectivityManager.getNetworkCapabilities(
@@ -235,13 +255,12 @@
hasAlarm ? buildAlarmContentDescription(alarm) : null);
}
- private void updateLowLightState() {
- int visibility = View.VISIBLE;
- if (mDreamOverlayStateController.isLowLightActive()
- || mStatusBarWindowStateController.windowIsShowing()) {
- visibility = View.INVISIBLE;
+ private void updateVisibility() {
+ if (shouldShowStatusBar()) {
+ mView.setVisibility(View.VISIBLE);
+ } else {
+ mView.setVisibility(View.INVISIBLE);
}
- mView.setVisibility(visibility);
}
private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
@@ -298,21 +317,11 @@
}
private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
- mMainExecutor.execute(() -> {
- if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
- return;
- }
+ if (!mIsAttached || !mEntryAnimationsFinished) {
+ return;
+ }
- switch (state) {
- case WINDOW_STATE_SHOWING:
- mView.setVisibility(View.INVISIBLE);
- break;
- case WINDOW_STATE_HIDING:
- case WINDOW_STATE_HIDDEN:
- mView.setVisibility(View.VISIBLE);
- break;
- }
- });
+ mMainExecutor.execute(this::updateVisibility);
}
private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index fd6cfc0..100ccc3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -28,6 +28,7 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.util.ViewController;
import java.util.Collection;
@@ -49,20 +50,34 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ComplicationLayoutEngine mLayoutEngine;
+ private final DreamOverlayStateController mDreamOverlayStateController;
private final LifecycleOwner mLifecycleOwner;
private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+ // Whether dream entry animations are finished.
+ private boolean mEntryAnimationsFinished = false;
+
@Inject
protected ComplicationHostViewController(
@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
ComplicationLayoutEngine layoutEngine,
+ DreamOverlayStateController dreamOverlayStateController,
LifecycleOwner lifecycleOwner,
@Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
super(view);
mLayoutEngine = layoutEngine;
mLifecycleOwner = lifecycleOwner;
mComplicationCollectionViewModel = viewModel;
+ mDreamOverlayStateController = dreamOverlayStateController;
+
+ mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ mEntryAnimationsFinished =
+ mDreamOverlayStateController.areEntryAnimationsFinished();
+ }
+ });
}
@Override
@@ -123,6 +138,11 @@
final ComplicationId id = complication.getId();
final Complication.ViewHolder viewHolder = complication.getComplication()
.createView(complication);
+ // Complications to be added before dream entry animations are finished are set
+ // to invisible and are animated in.
+ if (!mEntryAnimationsFinished) {
+ viewHolder.getView().setVisibility(View.INVISIBLE);
+ }
mComplications.put(id, viewHolder);
if (viewHolder.getView().getParent() != null) {
Log.e(TAG, "View for complication "
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4fe1622..cb012fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -47,6 +47,14 @@
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
"burn_in_protection_update_interval";
public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
+ public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration";
+ public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay";
+ public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION =
+ "dream_in_complications_anim_duration";
+ public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY =
+ "dream_in_top_complications_anim_delay";
+ public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
+ "dream_in_bottom_complications_anim_delay";
/** */
@Provides
@@ -114,6 +122,51 @@
return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter);
}
+ /**
+ * Duration in milliseconds of the dream in un-blur animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
+ static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in un-blur animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
+ static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+ }
+
+ /**
+ * Duration in milliseconds of the dream in complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+ static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in top complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+ static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in bottom complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+ static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ }
+
@Provides
@DreamOverlayComponent.DreamOverlayScope
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3f7a5ecb..09d7c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -125,6 +125,14 @@
// TODO(b/255607168): Tracking Bug
@JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213)
+ /**
+ * Whether to enable the code powering customizable lock screen quick affordances.
+ *
+ * Note that this flag does not enable individual implementations of quick affordances like the
+ * new camera quick affordance. Look for individual flags for those.
+ */
+ @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(214, teamfood = false)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -171,10 +179,18 @@
@Deprecated("Replaced by mobile and wifi specific flags.")
val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+ // TODO(b/256614753): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606)
+ // TODO(b/256614210): Tracking Bug
val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607)
+ // TODO(b/256614751): Tracking Bug
+ val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = UnreleasedFlag(608)
+
+ // TODO(b/256613548): Tracking Bug
+ val NEW_STATUS_BAR_WIFI_ICON_BACKEND = UnreleasedFlag(609)
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
@@ -327,7 +343,7 @@
val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true)
// 1700 - clipboard
- @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
// 1800 - shade container
@@ -336,6 +352,9 @@
// 1900 - note task
@JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+ // 2000 - device controls
+ @Keep val USE_APP_PANELS = UnreleasedFlag(2000, true)
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
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 56a1f1a..f08463b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
@@ -71,6 +72,7 @@
KeyguardUserSwitcherComponent.class},
includes = {
FalsingModule.class,
+ KeyguardDataQuickAffordanceModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
StartKeyguardTransitionModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 99ae85d..80c6130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -18,6 +18,7 @@
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -45,4 +46,9 @@
fun dispatchBackKeyEventPreIme(): Boolean
fun showNextSecurityScreenOrFinish(): Boolean
fun resume()
+ fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?,
+ )
+ fun willDismissWithActions(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index c600e13..d6f521c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -53,6 +53,10 @@
override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ override val pickerName: String by lazy { context.getString(component.getTileTitleId()) }
+
+ override val pickerIconResourceId: Int by lazy { component.getTileImageId() }
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
new file mode 100644
index 0000000..bea9363
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+
+@Module
+object KeyguardDataQuickAffordanceModule {
+ @Provides
+ @ElementsIntoSet
+ fun quickAffordanceConfigs(
+ home: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ ): Set<KeyguardQuickAffordanceConfig> {
+ return setOf(
+ home,
+ quickAccessWallet,
+ qrCodeScanner,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 0a8090b..fd40d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -29,6 +29,10 @@
/** Unique identifier for this quick affordance. It must be globally unique. */
val key: String
+ val pickerName: String
+
+ val pickerIconResourceId: Int
+
/**
* The ever-changing state of the affordance.
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
new file mode 100644
index 0000000..9c9354f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?".
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
+
+ // TODO(b/254858695): implement a persistence layer (database).
+ private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+ /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
+ val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+
+ /**
+ * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
+ * descending priority order.
+ */
+ suspend fun getSelections(): Map<String, List<String>> {
+ return _selections.value
+ }
+
+ /**
+ * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+ * IDs should be descending priority order.
+ */
+ suspend fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
+ // when we set its value to the same instance of the original map, even if we change the
+ // map by updating the value of one of its keys.
+ val copy = _selections.value.toMutableMap()
+ copy[slotId] = affordanceIds
+ _selections.value = copy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index d620b2a..11f72ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -24,6 +25,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -34,11 +36,16 @@
class QrCodeScannerKeyguardQuickAffordanceConfig
@Inject
constructor(
+ @Application context: Context,
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ override val pickerName = context.getString(R.string.qr_code_scanner_title)
+
+ override val pickerIconResourceId = R.drawable.ic_qr_code_scanner
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index be57a32..303e6a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -29,6 +30,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import javax.inject.Inject
@@ -40,12 +42,17 @@
class QuickAccessWalletKeyguardQuickAffordanceConfig
@Inject
constructor(
+ @Application context: Context,
private val walletController: QuickAccessWalletController,
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ override val pickerName = context.getString(R.string.accessibility_wallet_button)
+
+ override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 543389e..0046256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -21,10 +21,9 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -41,9 +40,15 @@
/** Determines if we want to instantaneously show the bouncer instead of translating. */
private val _isScrimmed = MutableStateFlow(false)
val isScrimmed = _isScrimmed.asStateFlow()
- /** Set amount of how much of the bouncer is showing on the screen */
- private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
- val expansionAmount = _expansionAmount.asStateFlow()
+ /**
+ * Set how much of the panel is showing on the screen.
+ * ```
+ * 0f = panel fully hidden = bouncer fully showing
+ * 1f = panel fully showing = bouncer fully hidden
+ * ```
+ */
+ private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
+ val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _isVisible = MutableStateFlow(false)
val isVisible = _isVisible.asStateFlow()
private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
@@ -54,8 +59,6 @@
val hide = _hide.asStateFlow()
private val _startingToHide = MutableStateFlow(false)
val startingToHide = _startingToHide.asStateFlow()
- private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
- val onDismissAction = _onDismissAction.asStateFlow()
private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
val startingDisappearAnimation = _disappearAnimation.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
@@ -96,8 +99,8 @@
_isScrimmed.value = isScrimmed
}
- fun setExpansion(expansion: Float) {
- _expansionAmount.value = expansion
+ fun setPanelExpansion(panelExpansion: Float) {
+ _panelExpansionAmount.value = panelExpansion
}
fun setVisible(isVisible: Boolean) {
@@ -120,10 +123,6 @@
_startingToHide.value = startingToHide
}
- fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
- _onDismissAction.value = bouncerCallbackActionsModel
- }
-
fun setStartDisappearAnimation(runnable: Runnable?) {
_disappearAnimation.value = runnable
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..95f614f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Abstracts access to application state related to keyguard quick affordances. */
+@SysUISingleton
+class KeyguardQuickAffordanceRepository
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+) {
+ /**
+ * List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
+ * given ID. The configs are sorted in descending priority order.
+ */
+ val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
+ selectionManager.selections
+ .map { selectionsBySlotId ->
+ selectionsBySlotId.mapValues { (_, selections) ->
+ configs.filter { selections.contains(it.key) }
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyMap(),
+ )
+
+ /**
+ * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
+ * slot with the given ID. The configs are sorted in descending priority order.
+ */
+ suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+ return configs.filter { selections.contains(it.key) }
+ }
+
+ /**
+ * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
+ * are sorted in descending priority order.
+ */
+ suspend fun getSelections(): Map<String, List<String>> {
+ return selectionManager.getSelections()
+ }
+
+ /**
+ * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+ * IDs should be descending priority order.
+ */
+ fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ scope.launch(backgroundDispatcher) {
+ selectionManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIds,
+ )
+ }
+ }
+
+ /**
+ * Returns the list of representation objects for all known affordances, regardless of what is
+ * selected. This is useful for building experiences like the picker/selector or user settings
+ * so the user can see everything that can be selected in a menu.
+ */
+ fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+ return configs.map { config ->
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config.key,
+ name = config.pickerName,
+ iconResourceId = config.pickerIconResourceId,
+ )
+ }
+ }
+
+ /**
+ * Returns the list of representation objects for all available slots on the keyguard. This is
+ * useful for building experiences like the picker/selector or user settings so the user can see
+ * each slot and select which affordance(s) is/are installed in each slot on the keyguard.
+ */
+ fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+ // TODO(b/256195304): source these from a config XML file.
+ return listOf(
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 6baaf5f..c867c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -23,9 +23,12 @@
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -65,6 +68,9 @@
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Observable for whether the bouncer is showing. */
+ val isBouncerShowing: Flow<Boolean>
+
/**
* Observable for whether we are in doze state.
*
@@ -95,6 +101,9 @@
/** Observable for device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel>
+ /** Observable for biometric unlock modes */
+ val biometricUnlockState: Flow<BiometricUnlockModel>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -125,6 +134,7 @@
private val keyguardStateController: KeyguardStateController,
dozeHost: DozeHost,
wakefulnessLifecycle: WakefulnessLifecycle,
+ biometricUnlockController: BiometricUnlockController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -159,6 +169,29 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onBouncerShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "updated isBouncerShowing"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "initial isBouncerShowing"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
override val isDozing: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
@@ -248,6 +281,24 @@
awaitClose { wakefulnessLifecycle.removeObserver(callback) }
}
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+ val callback =
+ object : BiometricUnlockController.BiometricModeListener {
+ override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+ trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+ }
+ }
+
+ biometricUnlockController.addBiometricModeListener(callback)
+ trySendWithFailureLogging(
+ biometricModeIntToObject(biometricUnlockController.getMode()),
+ TAG,
+ "initial biometric mode"
+ )
+
+ awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -279,6 +330,20 @@
}
}
+ private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+ return when (value) {
+ 0 -> BiometricUnlockModel.NONE
+ 1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
+ 2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ 3 -> BiometricUnlockModel.SHOW_BOUNCER
+ 4 -> BiometricUnlockModel.ONLY_WAKE
+ 5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
+ 6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ 7 -> BiometricUnlockModel.DISMISS_BOUNCER
+ else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..7e01db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodToGoneTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("AOD->GONE") {
+
+ private val wakeAndUnlockModes =
+ setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (biometricUnlockState, keyguardState) = pair
+ if (
+ keyguardState == KeyguardState.AOD &&
+ wakeAndUnlockModes.contains(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.AOD,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 2af9318..dbb0352 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -30,7 +30,6 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -40,6 +39,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -77,7 +77,7 @@
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
errorMessage = repository.bouncerErrorMessage,
- expansionAmount = repository.expansionAmount.value
+ expansionAmount = repository.panelExpansionAmount.value
)
)
repository.setShowingSoon(false)
@@ -90,14 +90,22 @@
val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
val isVisible: Flow<Boolean> = repository.isVisible
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
- val expansionAmount: Flow<Float> = repository.expansionAmount
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
repository.startingDisappearAnimation.filterNotNull()
- val onDismissAction: Flow<BouncerCallbackActionsModel> =
- repository.onDismissAction.filterNotNull()
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition
+ val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+ /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+ val bouncerExpansion: Flow<Float> = //
+ combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
+ ->
+ if (isVisible) {
+ 1f - expansionAmount
+ } else {
+ 0f
+ }
+ }
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
@@ -128,7 +136,7 @@
Trace.beginSection("KeyguardBouncer#show")
repository.setScrimmed(isScrimmed)
if (isScrimmed) {
- setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
}
if (resumeBouncer) {
@@ -149,7 +157,6 @@
}
keyguardStateController.notifyBouncerShowing(true)
callbackInteractor.dispatchStartingToShow()
-
Trace.endSection()
}
@@ -168,7 +175,6 @@
keyguardStateController.notifyBouncerShowing(false /* showing */)
cancelShowRunnable()
repository.setShowingSoon(false)
- repository.setOnDismissAction(null)
repository.setVisible(false)
repository.setHide(true)
repository.setShow(null)
@@ -176,14 +182,17 @@
}
/**
- * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
- * where 0f => showing and 1f => hiding
+ * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+ * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+ * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+ * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+ * of 0f represents the bouncer fully showing.
*/
- fun setExpansion(expansion: Float) {
- val oldExpansion = repository.expansionAmount.value
+ fun setPanelExpansion(expansion: Float) {
+ val oldExpansion = repository.panelExpansionAmount.value
val expansionChanged = oldExpansion != expansion
if (repository.startingDisappearAnimation.value == null) {
- repository.setExpansion(expansion)
+ repository.setPanelExpansion(expansion)
}
if (
@@ -227,7 +236,7 @@
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
- repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
/** Update the resources of the views. */
@@ -282,7 +291,7 @@
/** Returns whether bouncer is fully showing. */
fun isFullyShowing(): Boolean {
return (repository.showingSoon.value || repository.isVisible.value) &&
- repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+ repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
repository.startingDisappearAnimation.value == null
}
@@ -294,8 +303,8 @@
/** If bouncer expansion is between 0f and 1f non-inclusive. */
fun isInTransit(): Boolean {
return repository.showingSoon.value ||
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
}
/** Return whether bouncer is animating away. */
@@ -305,7 +314,7 @@
/** Return whether bouncer will dismiss with actions */
fun willDismissWithAction(): Boolean {
- return repository.onDismissAction.value?.onDismissAction != null
+ return bouncerView.delegate?.willDismissWithActions() == true
}
/** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 03c6a78..614ff8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -39,10 +41,19 @@
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
- /** Whether the keyguard is showing to not. */
+ /** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the bouncer is showing or not. */
+ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+ /** Observable for the [StatusBarState] */
+ val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /**
+ * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
+ * side, under display) is used to unlock the device.
+ */
+ val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 13d97aa..92caa89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -18,19 +18,30 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import dagger.Lazy
import javax.inject.Inject
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.onStart
@SysUISingleton
@@ -43,7 +54,12 @@
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
+ private val featureFlags: FeatureFlags,
+ private val repository: Lazy<KeyguardQuickAffordanceRepository>,
) {
+ private val isUsingRepository: Boolean
+ get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
position: KeyguardQuickAffordancePosition
@@ -72,7 +88,19 @@
configKey: String,
expandable: Expandable?,
) {
- @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+ @Suppress("UNCHECKED_CAST")
+ val config =
+ if (isUsingRepository) {
+ val (slotId, decodedConfigKey) = configKey.decode()
+ repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
+ } else {
+ registry.get(configKey)
+ }
+ if (config == null) {
+ Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
+ return
+ }
+
when (val result = config.onTriggered(expandable)) {
is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
launchQuickAffordance(
@@ -84,28 +112,138 @@
}
}
+ /**
+ * Selects an affordance with the given ID on the slot with the given ID.
+ *
+ * @return `true` if the affordance was selected successfully; `false` otherwise.
+ */
+ suspend fun select(slotId: String, affordanceId: String): Boolean {
+ check(isUsingRepository)
+
+ val slots = repository.get().getSlotPickerRepresentations()
+ val slot = slots.find { it.id == slotId } ?: return false
+ val selections =
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ val alreadySelected = selections.remove(affordanceId)
+ if (!alreadySelected) {
+ while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
+ selections.removeAt(0)
+ }
+ }
+
+ selections.add(affordanceId)
+
+ repository
+ .get()
+ .setSelections(
+ slotId = slotId,
+ affordanceIds = selections,
+ )
+
+ return true
+ }
+
+ /**
+ * Unselects one or all affordances from the slot with the given ID.
+ *
+ * @param slotId The ID of the slot.
+ * @param affordanceId The ID of the affordance to remove; if `null`, removes all affordances
+ * from the slot.
+ * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
+ * the affordance was not on the slot to begin with).
+ */
+ suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+ check(isUsingRepository)
+
+ val slots = repository.get().getSlotPickerRepresentations()
+ if (slots.find { it.id == slotId } == null) {
+ return false
+ }
+
+ if (affordanceId.isNullOrEmpty()) {
+ return if (
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+ ) {
+ false
+ } else {
+ repository.get().setSelections(slotId = slotId, affordanceIds = emptyList())
+ true
+ }
+ }
+
+ val selections =
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ return if (selections.remove(affordanceId)) {
+ repository
+ .get()
+ .setSelections(
+ slotId = slotId,
+ affordanceIds = selections,
+ )
+ true
+ } else {
+ false
+ }
+ }
+
+ /** Returns affordance IDs indexed by slot ID, for all known slots. */
+ suspend fun getSelections(): Map<String, List<String>> {
+ check(isUsingRepository)
+
+ val selections = repository.get().getSelections()
+ return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
+ slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
+ }
+ }
+
private fun quickAffordanceInternal(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
- val configs = registry.getAll(position)
+ return if (isUsingRepository) {
+ repository
+ .get()
+ .selections
+ .map { it[position.toSlotId()] ?: emptyList() }
+ .flatMapLatest { configs -> combinedConfigs(position, configs) }
+ } else {
+ combinedConfigs(position, registry.getAll(position))
+ }
+ }
+
+ private fun combinedConfigs(
+ position: KeyguardQuickAffordancePosition,
+ configs: List<KeyguardQuickAffordanceConfig>,
+ ): Flow<KeyguardQuickAffordanceModel> {
+ if (configs.isEmpty()) {
+ return flowOf(KeyguardQuickAffordanceModel.Hidden)
+ }
+
return combine(
configs.map { config ->
- // We emit an initial "Hidden" value to make sure that there's always an initial
- // value and avoid subtle bugs where the downstream isn't receiving any values
- // because one config implementation is not emitting an initial value. For example,
- // see b/244296596.
+ // We emit an initial "Hidden" value to make sure that there's always an
+ // initial value and avoid subtle bugs where the downstream isn't receiving
+ // any values because one config implementation is not emitting an initial
+ // value. For example, see b/244296596.
config.lockScreenState.onStart {
emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
}
) { states ->
val index =
- states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
+ states.indexOfFirst { state ->
+ state is KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ }
if (index != -1) {
val visibleState =
states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ val configKey = configs[index].key
KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index].key,
+ configKey =
+ if (isUsingRepository) {
+ configKey.encode(position.toSlotId())
+ } else {
+ configKey
+ },
icon = visibleState.icon,
activationState = visibleState.activationState,
)
@@ -145,4 +283,39 @@
)
}
}
+
+ private fun KeyguardQuickAffordancePosition.toSlotId(): String {
+ return when (this) {
+ KeyguardQuickAffordancePosition.BOTTOM_START ->
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ KeyguardQuickAffordancePosition.BOTTOM_END ->
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+ }
+ }
+
+ private fun String.encode(slotId: String): String {
+ return "$slotId$DELIMITER$this"
+ }
+
+ private fun String.decode(): Pair<String, String> {
+ val splitUp = this.split(DELIMITER)
+ return Pair(splitUp[0], splitUp[1])
+ }
+
+ fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+ check(isUsingRepository)
+
+ return repository.get().getAffordancePickerRepresentations()
+ }
+
+ fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+ check(isUsingRepository)
+
+ return repository.get().getSlotPickerRepresentations()
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceInteractor"
+ private const val DELIMITER = "::"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 83d9432..57fb4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,11 +41,13 @@
}
scope.launch {
- interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) }
+ interactor.finishedKeyguardTransitionStep.collect {
+ logger.i("Finished transition", it)
+ }
}
scope.launch {
- interactor.startedKeyguardState.collect { logger.i("Started transition to", it) }
+ interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index d5ea77b..a7c6d44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -41,6 +41,7 @@
is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index dffd097..749183e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,7 +21,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,8 +43,9 @@
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
- /** GONE->AOD information. */
- val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+ /** (any)->AOD transition information */
+ val anyStateToAodTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == KeyguardState.AOD }
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -57,15 +57,15 @@
lockscreenToAodTransition,
)
+ /* The last [TransitionStep] with a [TransitionState] of FINISHED */
+ val finishedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.FINISHED }
- .map { step -> step.to }
+ finishedKeyguardTransitionStep.map { step -> step.to }
- /* The last started [KeyguardState] transition */
- val startedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.STARTED }
- .map { step -> step.to }
+ /* The last [TransitionStep] with a [TransitionState] of STARTED */
+ val startedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 761f3fd..fd4814d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -16,14 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
@@ -38,7 +40,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
- private val keyguardRepository: KeyguardRepository,
+ private val keyguardInteractor: KeyguardInteractor,
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -47,12 +49,47 @@
private var transitionId: UUID? = null
override fun start() {
+ listenForDraggingUpToBouncer()
+ listenForBouncerHiding()
+ }
+
+ private fun listenForBouncerHiding() {
+ scope.launch {
+ keyguardInteractor.isBouncerShowing
+ .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isBouncerShowing, wakefulnessState) = pair
+ if (!isBouncerShowing) {
+ val to =
+ if (
+ wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
+ wakefulnessState == WakefulnessModel.ASLEEP
+ ) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.BOUNCER,
+ to = to,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+ private fun listenForDraggingUpToBouncer() {
scope.launch {
shadeRepository.shadeModel
.sample(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
- keyguardRepository.statusBarState,
+ keyguardInteractor.statusBarState,
) { keyguardState, statusBarState ->
Pair(keyguardState, statusBarState)
},
@@ -100,4 +137,15 @@
}
}
}
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 728bafa..37f33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -42,6 +42,8 @@
@Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+
@Binds
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
new file mode 100644
index 0000000..db709b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.shared.model
+
+/** Model device wakefulness states. */
+enum class BiometricUnlockModel {
+ /** Mode in which we don't need to wake up the device when we authenticate. */
+ NONE,
+ /**
+ * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a
+ * fingerprint while the screen is off and the device was sleeping.
+ */
+ WAKE_AND_UNLOCK,
+ /**
+ * Mode in which we wake the device up, and fade out the Keyguard contents because they were
+ * already visible while pulsing in doze mode.
+ */
+ WAKE_AND_UNLOCK_PULSING,
+ /**
+ * Mode in which we wake up the device, but play the normal dismiss animation. Active when we
+ * acquire a fingerprint pulsing in doze mode.
+ */
+ SHOW_BOUNCER,
+ /**
+ * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
+ */
+ ONLY_WAKE,
+ /**
+ * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
+ * device while being requested when keyguard is occluded or showing.
+ */
+ UNLOCK_COLLAPSING,
+ /** When bouncer is visible and will be dismissed. */
+ DISMISS_BOUNCER,
+ /** Mode in which fingerprint wakes and unlocks the device from a dream. */
+ WAKE_AND_UNLOCK_FROM_DREAM,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
new file mode 100644
index 0000000..a56bc90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.shared.model
+
+import androidx.annotation.DrawableRes
+
+/**
+ * Representation of a quick affordance for use to build "picker", "selector", or "settings"
+ * experiences.
+ */
+data class KeyguardQuickAffordancePickerRepresentation(
+ val id: String,
+ val name: String,
+ @DrawableRes val iconResourceId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
new file mode 100644
index 0000000..86f2756
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.shared.model
+
+/**
+ * Representation of a quick affordance slot (or position) for use to build "picker", "selector", or
+ * "settings" experiences.
+ */
+data class KeyguardSlotPickerRepresentation(
+ val id: String,
+ /** The maximum number of selected affordances that can be present on this slot. */
+ val maxSelectedAffordances: Int = 1,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0ca3582..732a6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -21,10 +21,11 @@
val to: KeyguardState = KeyguardState.NONE,
val value: Float = 0f, // constrained [0.0, 1.0]
val transitionState: TransitionState = TransitionState.FINISHED,
+ val ownerName: String = "",
) {
constructor(
info: TransitionInfo,
value: Float,
transitionState: TransitionState,
- ) : this(info.from, info.to, value, transitionState)
+ ) : this(info.from, info.to, value, transitionState, info.ownerName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index df26014..a22958b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.collect
@@ -75,6 +76,17 @@
hostViewController.showPrimarySecurityScreen()
hostViewController.onResume()
}
+
+ override fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?
+ ) {
+ hostViewController.setOnDismissAction(onDismissAction, cancelAction)
+ }
+
+ override fun willDismissWithActions(): Boolean {
+ return hostViewController.hasDismissActions()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,15 +134,6 @@
}
launch {
- viewModel.setDismissAction.collect {
- hostViewController.setOnDismissAction(
- it.onDismissAction,
- it.cancelAction
- )
- }
- }
-
- launch {
viewModel.startDisappearAnimation.collect {
hostViewController.startDisappearAnimation(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9ad5211..9a92843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,7 +20,6 @@
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -38,7 +37,7 @@
private val interactor: BouncerInteractor,
) {
/** Observe on bouncer expansion amount. */
- val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+ val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
@@ -63,9 +62,6 @@
/** Observe whether bouncer is starting to hide. */
val startingToHide: Flow<Unit> = interactor.startingToHide
- /** Observe whether we want to set the dismiss action to the bouncer. */
- val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
-
/** Observe whether we want to start the disappear animation. */
val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c089511..85d15dc 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -85,7 +85,11 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -354,15 +358,6 @@
}
@Override
- public void onQuickStepStarted() {
- // Use navbar dragging as a signal to hide the rotate button
- mView.getRotationButtonController().setRotateSuggestionButtonState(false);
-
- // Hide the notifications panel when quick step starts
- mShadeController.collapsePanel(true /* animate */);
- }
-
- @Override
public void onPrioritizedRotation(@Surface.Rotation int rotation) {
mStartingQuickSwitchRotation = rotation;
if (rotation == -1) {
@@ -475,6 +470,24 @@
}
};
+ private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
+ new SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceReplaced(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+ };
+
@Inject
NavigationBar(
NavigationBarView navigationBarView,
@@ -680,7 +693,8 @@
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ mView.setComponents(
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController());
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -701,6 +715,8 @@
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
mOnComputeInternalInsetsListener);
+ mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+ notifyNavigationBarSurface();
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -779,6 +795,10 @@
mHandler.removeCallbacks(mEnableLayoutTransitions);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+ }
mFrame = null;
mOrientationHandle = null;
}
@@ -932,6 +952,12 @@
}
}
+ private void notifyNavigationBarSurface() {
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null;
+ mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
+ }
+
private int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
@@ -1257,8 +1283,8 @@
}
private void onVerticalChanged(boolean isVertical) {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- statusBar -> statusBar.setQsScrimEnabled(!isVertical));
+ mCentralSurfacesOptionalLazy.get().ifPresent(statusBar ->
+ statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical));
}
private boolean onNavigationTouch(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 622f5a2..83c2a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -412,10 +412,6 @@
logSomePresses(action, flags);
if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
- if (action == MotionEvent.ACTION_UP) {
- mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
- -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
- }
}
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 709467f..c319a82 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -287,8 +287,6 @@
mBackAnimation.setTriggerBack(true);
}
- mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
logGesture(mInRejectedExclusion
? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
@@ -300,8 +298,6 @@
mBackAnimation.setTriggerBack(false);
}
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
- mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
}
@Override
@@ -785,9 +781,6 @@
if (mExcludeRegion.contains(x, y)) {
if (withinRange) {
- // Log as exclusion only if it is in acceptable range in the first place.
- mOverviewProxyService.notifyBackAction(
- false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
// We don't have the end point for logging purposes.
mEndPoint.x = -1;
mEndPoint.y = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e9a6c25..1f92b12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,7 +140,7 @@
iv.setTag(R.id.qs_icon_tag, icon);
iv.setTag(R.id.qs_slash_tag, state.slash);
iv.setPadding(0, padding, 0, padding);
- if (d instanceof Animatable2) {
+ if (shouldAnimate && d instanceof Animatable2) {
Animatable2 a = (Animatable2) d;
a.start();
if (state.isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66be00d..46c4f41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,6 +64,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
@@ -149,6 +150,7 @@
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
+ private SurfaceControl mNavigationBarSurface;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
@@ -190,7 +192,8 @@
// TODO move this logic to message queue
mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
if (event.getActionMasked() == ACTION_DOWN) {
- centralSurfaces.getPanelController().startExpandLatencyTracking();
+ centralSurfaces.getNotificationPanelViewController()
+ .startExpandLatencyTracking();
}
mHandler.post(() -> {
int action = event.getActionMasked();
@@ -217,17 +220,15 @@
}
@Override
- public void onBackPressed() throws RemoteException {
+ public void onBackPressed() {
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-
- notifyBackAction(true, -1, -1, true, false);
});
}
@Override
- public void onImeSwitcherPressed() throws RemoteException {
+ public void onImeSwitcherPressed() {
// TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
// Launcher/Taskbar isn't display aware.
mContext.getSystemService(InputMethodManager.class)
@@ -316,12 +317,6 @@
}
@Override
- public void notifySwipeUpGestureStarted() {
- verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
- notifySwipeUpGestureStartedInternal());
- }
-
- @Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
notifyPrioritizedRotationInternal(rotation));
@@ -443,6 +438,7 @@
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
+ dispatchNavigationBarSurface();
// Force-update the systemui state flags
updateSystemUiStateFlags();
@@ -597,11 +593,18 @@
.commitUpdate(mContext.getDisplayId());
}
- public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) {
+ /**
+ * Called when the navigation bar surface is created or changed
+ */
+ public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
+ mNavigationBarSurface = navbarSurface;
+ dispatchNavigationBarSurface();
+ }
+
+ private void dispatchNavigationBarSurface() {
try {
if (mOverviewProxy != null) {
- mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+ mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify back action", e);
@@ -614,7 +617,7 @@
final NavigationBarView navBarView =
mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
final NotificationPanelViewController panelController =
- mCentralSurfacesOptionalLazy.get().get().getPanelController();
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController();
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
+ " navBarView=" + navBarView + " panelController=" + panelController);
@@ -800,24 +803,12 @@
}
}
- public void notifyQuickStepStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickStepStarted();
- }
- }
-
private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
}
}
- public void notifyQuickScrubStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickScrubStarted();
- }
- }
-
private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onAssistantProgress(progress);
@@ -836,12 +827,6 @@
}
}
- private void notifySwipeUpGestureStartedInternal() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
- }
- }
-
public void notifyAssistantVisibilityChanged(float visibility) {
try {
if (mOverviewProxy != null) {
@@ -1005,23 +990,20 @@
pw.print(" mWindowCornerRadius="); pw.println(mWindowCornerRadius);
pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
+ pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
mSysUiState.dump(pw, args);
}
public interface OverviewProxyListener {
default void onConnectionChanged(boolean isConnected) {}
- default void onQuickStepStarted() {}
- default void onSwipeUpGestureStarted() {}
default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
default void onOverviewShown(boolean fromHome) {}
- default void onQuickScrubStarted() {}
/** Notify the recents app (overview) is started by 3-button navigation. */
default void onToggleRecentApps() {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
default void onTaskbarAutohideSuspend(boolean suspend) {}
- default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
default void startAssistant(Bundle bundle) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e00f5eba..92f5c85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -531,7 +531,7 @@
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
- private KeyguardIndicationController mKeyguardIndicationController;
+ private final KeyguardIndicationController mKeyguardIndicationController;
private int mHeadsUpInset;
private boolean mHeadsUpPinnedMode;
private boolean mAllowExpandForSmallExpansion;
@@ -743,6 +743,7 @@
SysUiState sysUiState,
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@@ -779,6 +780,7 @@
mResources = mView.getResources();
mKeyguardStateController = keyguardStateController;
+ mKeyguardIndicationController = keyguardIndicationController;
mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mNotificationShadeWindowController = notificationShadeWindowController;
FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get();
@@ -1020,7 +1022,7 @@
mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
- mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+ setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
initBottomArea();
@@ -1264,7 +1266,7 @@
int index = mView.indexOfChild(mKeyguardBottomArea);
mView.removeView(mKeyguardBottomArea);
KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
- mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView();
+ setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
mKeyguardBottomArea.initFrom(oldBottomArea);
mView.addView(mKeyguardBottomArea, index);
initBottomArea();
@@ -1343,8 +1345,8 @@
return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
}
- public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
- mKeyguardIndicationController = indicationController;
+ private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
+ mKeyguardBottomArea = keyguardBottomArea;
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
}
@@ -3880,6 +3882,7 @@
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
NotificationPanelViewController.this);
@@ -4361,10 +4364,6 @@
mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
- public ShadeHeadsUpChangedListener getOnHeadsUpChangedListener() {
- return mOnHeadsUpChangedListener;
- }
-
public void setHeaderDebugInfo(String text) {
if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
}
@@ -4644,7 +4643,7 @@
mUpdateFlingVelocity = vel;
}
} else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
onEmptySpaceClick();
onTrackingStopped(true);
@@ -5682,6 +5681,7 @@
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
+ mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
if (SPEW_LOGCAT) {
Log.v(TAG,
"NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
@@ -5694,6 +5694,8 @@
// Do not let touches go to shade or QS if the bouncer is visible,
// but still let user swipe down to expand the panel, dismissing the bouncer.
if (mCentralSurfaces.isBouncerShowing()) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "bouncer is showing");
return true;
}
if (mCommandQueue.panelsEnabled()
@@ -5701,15 +5703,21 @@
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "HeadsUpTouchHelper");
return true;
}
if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
&& mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "PulseExpansionHandler");
return true;
}
if (!isFullyCollapsed() && onQsIntercept(event)) {
debugLog("onQsIntercept true");
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "QsIntercept");
return true;
}
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -5740,6 +5748,9 @@
if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
cancelHeightAnimator();
mTouchSlopExceeded = true;
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
+ + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:"
+ + " false");
return true;
}
mInitialExpandY = y;
@@ -5784,6 +5795,8 @@
&& hAbs > Math.abs(x - mInitialExpandX)) {
cancelHeightAnimator();
startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ mShadeLog.v("NotificationPanelViewController MotionEvent"
+ + " intercepted: startExpandMotion");
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 65bd58d..1e63b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -284,7 +284,7 @@
return true;
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// capture all touches if the alt auth bouncer is showing
return true;
}
@@ -322,7 +322,7 @@
handled = !mService.isPulsing();
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// eat the touch
handled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index 084b7dc..bf622c9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -52,6 +52,7 @@
private val centralSurfaces: CentralSurfaces,
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val statusBarStateController: StatusBarStateController,
+ private val shadeLogger: ShadeLogger,
tunerService: TunerService,
dumpManager: DumpManager
) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -77,18 +78,23 @@
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
- if (statusBarStateController.isDozing &&
- singleTapEnabled &&
- !dockManager.isDocked &&
- !falsingManager.isProximityNear &&
- !falsingManager.isFalseTap(LOW_PENALTY)
- ) {
- centralSurfaces.wakeUpIfDozing(
+ val isNotDocked = !dockManager.isDocked
+ shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked)
+ if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) {
+ val proximityIsNotNear = !falsingManager.isProximityNear
+ val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY)
+ shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
+ if (proximityIsNotNear && isNotAFalseTap) {
+ shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
+ centralSurfaces.wakeUpIfDozing(
SystemClock.uptimeMillis(),
notificationShadeWindowView,
- "PULSING_SINGLE_TAP")
+ "PULSING_SINGLE_TAP"
+ )
+ }
return true
}
+ shadeLogger.d("onSingleTapUp event ignored")
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index f389dd9..eaf7fae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -224,6 +224,6 @@
}
private NotificationPanelViewController getNotificationPanelViewController() {
- return getCentralSurfaces().getPanelController();
+ return getCentralSurfaces().getNotificationPanelViewController();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 7f1bba3..7615301 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -16,6 +16,10 @@
buffer.log(TAG, LogLevel.VERBOSE, msg)
}
+ fun d(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, LogLevel.DEBUG, msg)
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
@@ -123,4 +127,25 @@
"animatingQs=$long1"
})
}
+
+ fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = isDozing
+ bool2 = singleTapEnabled
+ bool3 = isNotDocked
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
+ })
+ }
+
+ fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = proximityIsNotNear
+ bool2 = isNotFalseTap
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 258f9fc..c070fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioAttributes;
+import android.os.Process;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -33,6 +34,7 @@
import javax.inject.Inject;
/**
+ *
*/
@SysUISingleton
public class VibratorHelper {
@@ -40,9 +42,18 @@
private final Vibrator mVibrator;
public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+
+ private static final VibrationEffect BIOMETRIC_SUCCESS_VIBRATION_EFFECT =
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ private static final VibrationEffect BIOMETRIC_ERROR_VIBRATION_EFFECT =
+ VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
+
private final Executor mExecutor;
/**
+ *
*/
@Inject
public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
@@ -109,4 +120,23 @@
}
mExecutor.execute(mVibrator::cancel);
}
+
+ /**
+ * Perform vibration when biometric authentication success
+ */
+ public void vibrateAuthSuccess(String reason) {
+ vibrate(Process.myUid(),
+ "com.android.systemui",
+ BIOMETRIC_SUCCESS_VIBRATION_EFFECT, reason,
+ HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
+ }
+
+ /**
+ * Perform vibration when biometric authentication error
+ */
+ public void vibrateAuthError(String reason) {
+ vibrate(Process.myUid(), "com.android.systemui",
+ BIOMETRIC_ERROR_VIBRATION_EFFECT, reason,
+ HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c4ef28e..2c3330e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -114,6 +114,8 @@
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.LargeScreenUtils;
+import com.google.errorprone.annotations.CompileTimeConstant;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -3693,6 +3695,8 @@
@ShadeViewRefactor(RefactorComponent.INPUT)
void handleEmptySpaceClick(MotionEvent ev) {
+ logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
+ mStatusBarState, mTouchIsClick);
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
final float touchSlop = getTouchSlop(ev);
@@ -3704,12 +3708,34 @@
case MotionEvent.ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
+ debugLog("handleEmptySpaceClick: touch event propagated further");
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
break;
+ default:
+ debugLog("handleEmptySpaceClick: MotionEvent ignored");
}
}
+ private void debugLog(@CompileTimeConstant String s) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.d(s);
+ }
+
+ private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
+ int statusBarState, boolean touchIsClick) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.logEmptySpaceClick(
+ isTouchBelowLastNotification,
+ statusBarState,
+ touchIsClick,
+ MotionEvent.actionToString(ev.getActionMasked()));
+ }
+
@ShadeViewRefactor(RefactorComponent.INPUT)
void initDownStates(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 4c52db7..64dd6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -2,6 +2,7 @@
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
@@ -10,6 +11,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
+import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@@ -56,6 +58,25 @@
"key: $str1 expected: $bool1 actual: $bool2"
})
}
+
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+
+ fun logEmptySpaceClick(
+ isBelowLastNotification: Boolean,
+ statusBarState: Int,
+ touchIsClick: Boolean,
+ motionEventDesc: String
+ ) {
+ buffer.log(TAG, DEBUG, {
+ int1 = statusBarState
+ bool1 = touchIsClick
+ bool2 = isBelowLastNotification
+ str1 = motionEventDesc
+ }, {
+ "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " +
+ "isTouchBelowNotification: $bool2 motionEvent: $str1"
+ })
+ }
}
private const val TAG = "NotificationStackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index a2798f4..6e68079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -27,11 +27,8 @@
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -70,8 +67,10 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -87,12 +86,6 @@
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
private static final int UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
- private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
- VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- private static final VibrationEffect ERROR_VIBRATION_EFFECT =
- VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
- private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
- VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@IntDef(prefix = { "MODE_" }, value = {
MODE_NONE,
@@ -167,7 +160,6 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final SessionTracker mSessionTracker;
private final int mConsecutiveFpFailureThreshold;
- private final boolean mShouldVibrate;
private int mMode;
private BiometricSourceType mBiometricType;
private KeyguardViewController mKeyguardViewController;
@@ -177,7 +169,7 @@
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
- private BiometricModeListener mBiometricModeListener;
+ private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
@@ -307,8 +299,6 @@
mHandler = handler;
mConsecutiveFpFailureThreshold = resources.getInteger(
R.integer.fp_consecutive_failure_time_ms);
- mShouldVibrate = !(resources.getBoolean(
- com.android.internal.R.bool.system_server_plays_face_haptics));
mKeyguardBypassController = keyguardBypassController;
mKeyguardBypassController.setUnlockController(this);
mMetricsLogger = metricsLogger;
@@ -326,9 +316,14 @@
mKeyguardViewController = keyguardViewController;
}
- /** Sets a {@link BiometricModeListener}. */
- public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
- mBiometricModeListener = biometricModeListener;
+ /** Adds a {@link BiometricModeListener}. */
+ public void addBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.add(listener);
+ }
+
+ /** Removes a {@link BiometricModeListener}. */
+ public void removeBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.remove(listener);
}
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -423,10 +418,10 @@
public void startWakeAndUnlock(BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
int mode = calculateMode(biometricSourceType, isStrongBiometric);
- if (BiometricSourceType.FACE == biometricSourceType && (mode == MODE_WAKE_AND_UNLOCK
+ if (mode == MODE_WAKE_AND_UNLOCK
|| mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
- || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER)) {
- vibrateSuccess();
+ || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
+ vibrateSuccess(biometricSourceType);
}
startWakeAndUnlock(mode);
}
@@ -511,15 +506,12 @@
break;
}
onModeChanged(mMode);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.notifyBiometricAuthModeChanged();
- }
Trace.endSection();
}
private void onModeChanged(@WakeAndUnlockMode int mode) {
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onModeChanged(mode);
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onModeChanged(mode);
}
}
@@ -659,10 +651,11 @@
}
// Suppress all face auth errors if fingerprint can be used to authenticate
- if (biometricSourceType == BiometricSourceType.FACE
+ if ((biometricSourceType == BiometricSourceType.FACE
&& !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())) {
- vibrateError();
+ KeyguardUpdateMonitor.getCurrentUser()))
+ || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
+ vibrateError(biometricSourceType);
}
cleanup();
@@ -688,24 +681,15 @@
cleanup();
}
- private void vibrateSuccess() {
- if (mShouldVibrate) {
- mVibratorHelper.vibrate(Process.myUid(),
- "com.android.systemui",
- SUCCESS_VIBRATION_EFFECT,
- getClass().getSimpleName() + "::success",
- HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
- }
+ //these haptics are for device-entry only
+ private void vibrateSuccess(BiometricSourceType type) {
+ mVibratorHelper.vibrateAuthSuccess(
+ getClass().getSimpleName() + ", type =" + type + "device-entry::success");
}
- private void vibrateError() {
- if (mShouldVibrate) {
- mVibratorHelper.vibrate(Process.myUid(),
- "com.android.systemui",
- ERROR_VIBRATION_EFFECT,
- getClass().getSimpleName() + "::error",
- HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
- }
+ private void vibrateError(BiometricSourceType type) {
+ mVibratorHelper.vibrateAuthError(
+ getClass().getSimpleName() + ", type =" + type + "device-entry::error");
}
private void cleanup() {
@@ -734,9 +718,8 @@
mMode = MODE_NONE;
mBiometricType = null;
mNotificationShadeWindowController.setForceDozeBrightness(false);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onResetMode();
- mBiometricModeListener.notifyBiometricAuthModeChanged();
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onResetMode();
}
mNumConsecutiveFpFailures = 0;
mLastFpFailureUptimeMillis = 0;
@@ -845,10 +828,8 @@
/** An interface to interact with the {@link BiometricUnlockController}. */
public interface BiometricModeListener {
/** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
- void onResetMode();
+ default void onResetMode() {}
/** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
- void onModeChanged(@WakeAndUnlockMode int mode);
- /** Called after processing {@link #onModeChanged(int)}. */
- void notifyBiometricAuthModeChanged();
+ default void onModeChanged(@WakeAndUnlockMode int mode) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 75b444f..dc37082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -196,8 +196,6 @@
void collapsePanelOnMainThread();
- void collapsePanelWithDuration(int duration);
-
void togglePanel();
void start();
@@ -305,9 +303,6 @@
void checkBarModes();
- // Called by NavigationBarFragment
- void setQsScrimEnabled(boolean scrimEnabled);
-
void updateBubblesVisibility();
void setInteracting(int barWindow, boolean interacting);
@@ -379,8 +374,6 @@
void showKeyguardImpl();
- boolean isInLaunchTransition();
-
void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
Runnable endRunnable, Runnable cancelRunnable);
@@ -437,8 +430,6 @@
void showPinningEscapeToast();
- KeyguardBottomAreaView getKeyguardBottomAreaView();
-
void setBouncerShowing(boolean bouncerShowing);
void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
@@ -505,12 +496,8 @@
boolean isBouncerShowingOverDream();
- void onBouncerPreHideAnimation();
-
boolean isKeyguardSecure();
- NotificationPanelViewController getPanelController();
-
NotificationGutsManager getGutsManager();
void updateNotificationPanelTouchState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 47a12a8..15cc086 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -483,7 +483,7 @@
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
- private final com.android.systemui.shade.ShadeController mShadeController;
+ private final ShadeController mShadeController;
private final InitController mInitController;
private final PluginDependencyProvider mPluginDependencyProvider;
@@ -496,9 +496,9 @@
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
- // expanded notifications
- // the sliding/resizing panel within the notification window
- protected NotificationPanelViewController mNotificationPanelViewController;
+ /** Controller for the Shade. */
+ @VisibleForTesting
+ NotificationPanelViewController mNotificationPanelViewController;
// settings
private QSPanelController mQSPanelController;
@@ -1169,7 +1169,6 @@
initializer.initializeStatusBar(mCentralSurfacesComponent);
mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
- mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
createNavigationBar(result);
@@ -1178,9 +1177,6 @@
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
-
mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
R.id.ambient_indication_container);
@@ -1531,11 +1527,12 @@
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
- mBiometricUnlockController.setBiometricModeListener(
+ mBiometricUnlockController.addBiometricModeListener(
new BiometricUnlockController.BiometricModeListener() {
@Override
public void onResetMode() {
setWakeAndUnlocking(false);
+ notifyBiometricAuthModeChanged();
}
@Override
@@ -1546,11 +1543,7 @@
case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
setWakeAndUnlocking(true);
}
- }
-
- @Override
- public void notifyBiometricAuthModeChanged() {
- CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+ notifyBiometricAuthModeChanged();
}
private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
@@ -2021,8 +2014,7 @@
}
void makeExpandedInvisible() {
- if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
- + " mExpandedVisible=" + mExpandedVisible);
+ if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
if (!mExpandedVisible || mNotificationShadeWindowView == null) {
return;
@@ -2198,12 +2190,6 @@
mNoAnimationOnNextBarModeChange = false;
}
- // Called by NavigationBarFragment
- @Override
- public void setQsScrimEnabled(boolean scrimEnabled) {
- mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
- }
-
/** Temporarily hides Bubbles if the status bar is hidden. */
@Override
public void updateBubblesVisibility() {
@@ -2579,14 +2565,11 @@
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */, true /* delayed*/);
} else {
-
// Do it after DismissAction has been processed to conserve the needed
// ordering.
mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
}
- } else if (CentralSurfacesImpl.this.isInLaunchTransition()
- && mNotificationPanelViewController.isLaunchTransitionFinished()) {
-
+ } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
// We are not dismissing the shade, but the launch transition is already
// finished,
// so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -3008,11 +2991,6 @@
mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
}
- @Override
- public boolean isInLaunchTransition() {
- return mNotificationPanelViewController.isLaunchTransitionFinished();
- }
-
/**
* Fades the content of the keyguard away after the launch transition is done.
*
@@ -3392,12 +3370,6 @@
}
}
- /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */
- @Override
- public void collapsePanelWithDuration(int duration) {
- mNotificationPanelViewController.collapseWithDuration(duration);
- }
-
/**
* Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
* from the power button).
@@ -3487,12 +3459,6 @@
mNavigationBarController.showPinningEscapeToast(mDisplayId);
}
- //TODO(b/254875405): this should be removed.
- @Override
- public KeyguardBottomAreaView getKeyguardBottomAreaView() {
- return mNotificationPanelViewController.getKeyguardBottomAreaView();
- }
-
protected ViewRootImpl getViewRootImpl() {
NotificationShadeWindowView nswv = getNotificationShadeWindowView();
if (nswv != null) return nswv.getViewRootImpl();
@@ -4195,23 +4161,11 @@
return mBouncerShowingOverDream;
}
- /**
- * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
- */
- @Override
- public void onBouncerPreHideAnimation() {
- mNotificationPanelViewController.startBouncerPreHideAnimation();
-
- }
-
@Override
public boolean isKeyguardSecure() {
return mStatusBarKeyguardViewManager.isSecure();
}
- @Override
- public NotificationPanelViewController getPanelController() {
- return mNotificationPanelViewController;
- }
+
// End Extra BaseStatusBarMethods.
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 9bb4132..b2a9509 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -64,6 +64,11 @@
private static final String TAG = "KeyguardBouncer";
static final long BOUNCER_FACE_DELAY = 1200;
public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
+ /**
+ * Values for the bouncer expansion represented as the panel expansion.
+ * Panel expansion 1f = panel fully showing = bouncer fully hidden
+ * Panel expansion 0f = panel fully hiding = bouncer fully showing
+ */
public static final float EXPANSION_HIDDEN = 1f;
public static final float EXPANSION_VISIBLE = 0f;
@@ -143,6 +148,14 @@
}
/**
+ * Get the KeyguardBouncer expansion
+ * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
+ */
+ public float getExpansion() {
+ return mExpansion;
+ }
+
+ /**
* Enable/disable only the back button
*/
public void setBackButtonEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 86f6ff8..0a0ded2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
import android.annotation.Nullable;
import android.content.Context;
@@ -53,8 +54,9 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
import com.android.systemui.util.Assert;
import java.util.ArrayList;
@@ -84,7 +86,18 @@
/** */
void setIcon(String slot, StatusBarIcon icon);
/** */
- void setSignalIcon(String slot, WifiIconState state);
+ void setWifiIcon(String slot, WifiIconState state);
+
+ /**
+ * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
+ * set up (inflated and added to the view hierarchy).
+ *
+ * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
+ * data pipeline. Icons will automatically keep their state up to date, so we don't have to
+ * worry about funneling state objects through anymore.
+ */
+ void setNewWifiIcon();
+
/** */
void setMobileIcons(String slot, List<MobileIconState> states);
@@ -151,14 +164,14 @@
LinearLayout linearLayout,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
@@ -218,7 +231,7 @@
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
+ private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@@ -226,12 +239,12 @@
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileContextProvider mobileContextProvider,
MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
+ mWifiUiAdapter = wifiUiAdapter;
mMobileContextProvider = mobileContextProvider;
mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
@@ -242,7 +255,7 @@
group,
location,
mStatusBarPipelineFlags,
- mWifiViewModel,
+ mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
@@ -260,14 +273,14 @@
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
super(group,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
}
@@ -302,19 +315,19 @@
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
+ private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
+ mWifiUiAdapter = wifiUiAdapter;
mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
@@ -324,7 +337,7 @@
group,
location,
mStatusBarPipelineFlags,
- mWifiViewModel,
+ mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider);
}
@@ -336,10 +349,9 @@
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
- private final StatusBarLocation mLocation;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final LocationBasedWifiViewModel mWifiViewModel;
private final MobileIconsViewModel mMobileIconsViewModel;
protected final Context mContext;
@@ -359,26 +371,33 @@
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mGroup = group;
- mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
- if (statusBarPipelineFlags.useNewMobileIcons()) {
- // This starts the flow for the new pipeline, and will notify us of changes
+ if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
+ // This starts the flow for the new pipeline, and will notify us of changes if
+ // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else {
mMobileIconsViewModel = null;
}
+
+ if (statusBarPipelineFlags.runNewWifiIconBackend()) {
+ // This starts the flow for the new pipeline, and will notify us of changes if
+ // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
+ mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+ } else {
+ mWifiViewModel = null;
+ }
}
public boolean isDemoable() {
@@ -429,6 +448,9 @@
case TYPE_WIFI:
return addWifiIcon(index, slot, holder.getWifiState());
+ case TYPE_WIFI_NEW:
+ return addNewWifiIcon(index, slot);
+
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
@@ -450,16 +472,13 @@
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- final BaseStatusBarFrameLayout view;
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
- view = onCreateModernStatusBarWifiView(slot);
- // When [ModernStatusBarWifiView] is created, it will automatically apply the
- // correct view state so we don't need to call applyWifiState.
- } else {
- StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot);
- wifiView.applyWifiState(state);
- view = wifiView;
+ throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ + "icons are enabled is not supported");
}
+
+ final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
+ view.applyWifiState(state);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
@@ -468,6 +487,17 @@
return view;
}
+ protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
+ if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+ throw new IllegalStateException("Attempting to add a wifi icon using the new"
+ + "pipeline, but the enabled flag is false.");
+ }
+
+ ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
+ mGroup.addView(view, index, onCreateLayoutParams());
+ return view;
+ }
+
@VisibleForTesting
protected StatusIconDisplayable addMobileIcon(
int index,
@@ -523,8 +553,7 @@
}
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
- return ModernStatusBarWifiView.constructAndBind(
- mContext, slot, mWifiViewModel, mLocation);
+ return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
@@ -600,7 +629,8 @@
onSetMobileIcon(viewIndex, holder.getMobileState());
return;
case TYPE_MOBILE_NEW:
- // Nothing, the icon updates itself now
+ case TYPE_WIFI_NEW:
+ // Nothing, the new icons update themselves
return;
default:
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 31e960a..674e574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -195,12 +195,13 @@
}
}
- /**
- * Signal icons need to be handled differently, because they can be
- * composite views
- */
@Override
- public void setSignalIcon(String slot, WifiIconState state) {
+ public void setWifiIcon(String slot, WifiIconState state) {
+ if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+ Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
+ return;
+ }
+
if (state == null) {
removeIcon(slot, 0);
return;
@@ -216,6 +217,24 @@
}
}
+
+ @Override
+ public void setNewWifiIcon() {
+ if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+ Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
+ return;
+ }
+
+ String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
+ StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
+ if (holder == null) {
+ holder = StatusBarIconHolder.forNewWifiIcon();
+ setIcon(slot, holder);
+ } else {
+ // Don't have to do anything in the new world
+ }
+ }
+
/**
* Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
* by subId. Don't worry this definitely makes sense and works.
@@ -225,7 +244,7 @@
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
if (mStatusBarPipelineFlags.useNewMobileIcons()) {
- Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+ Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
+ "icons are enabled");
return;
}
@@ -251,10 +270,11 @@
public void setNewMobileIconSubIds(List<Integer> subIds) {
if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
Log.d(TAG, "ignoring new pipeline callback, "
- + "since the new icons are disabled");
+ + "since the new mobile icons are disabled");
return;
}
- Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+ String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
+ Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
Collections.reverse(subIds);
@@ -262,7 +282,7 @@
StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
if (holder == null) {
holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
- setIcon("mobile", holder);
+ setIcon(slotName, holder);
} else {
// Don't have to do anything in the new world
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 68a203e..f6c0da8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -51,11 +51,24 @@
@Deprecated
public static final int TYPE_MOBILE_NEW = 3;
+ /**
+ * TODO (b/238425913): address this once the new pipeline is in place
+ * This type exists so that the new wifi pipeline can be used to inform the old view system
+ * about the existence of the wifi icon. The design of the new pipeline should allow for removal
+ * of this icon holder type, and obsolete the need for this entire class.
+ *
+ * @deprecated This field only exists so the new status bar pipeline can interface with the
+ * view holder system.
+ */
+ @Deprecated
+ public static final int TYPE_WIFI_NEW = 4;
+
@IntDef({
TYPE_ICON,
TYPE_WIFI,
TYPE_MOBILE,
- TYPE_MOBILE_NEW
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW
})
@Retention(RetentionPolicy.SOURCE)
@interface IconType {}
@@ -95,6 +108,13 @@
return holder;
}
+ /** Creates a new holder with for the new wifi icon. */
+ public static StatusBarIconHolder forNewWifiIcon() {
+ StatusBarIconHolder holder = new StatusBarIconHolder();
+ holder.mType = TYPE_WIFI_NEW;
+ return holder;
+ }
+
/** */
public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -172,9 +192,10 @@
case TYPE_MOBILE:
return mMobileState.visible;
case TYPE_MOBILE_NEW:
- //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ case TYPE_WIFI_NEW:
+ // The new pipeline controls visibilities via the view model and view binder, so
+ // this is effectively an unused return value.
return true;
-
default:
return true;
}
@@ -199,7 +220,9 @@
break;
case TYPE_MOBILE_NEW:
- //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ case TYPE_WIFI_NEW:
+ // The new pipeline controls visibilities via the view model and view binder, so
+ // ignore setVisible.
break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ccb5d88..a00e756 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -86,8 +86,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -166,13 +168,9 @@
@Override
public void onExpansionChanged(float expansion) {
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
- }
if (mBouncerAnimating) {
mCentralSurfaces.setBouncerHiddenFraction(expansion);
}
- updateStates();
}
@Override
@@ -184,9 +182,6 @@
if (!isVisible) {
mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
}
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.onBouncerVisibilityChanged();
- }
/* Register predictive back callback when keyguard becomes visible, and unregister
when it's hidden. */
@@ -252,6 +247,7 @@
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+ final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernBouncerEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -465,7 +461,7 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
@@ -475,17 +471,17 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
} else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
- && !mCentralSurfaces.isInLaunchTransition()
+ && !mNotificationPanelViewController.isLaunchTransitionFinished()
&& !isUnlockCollapsing()) {
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
} else {
- mBouncerInteractor.setExpansion(fraction);
+ mBouncerInteractor.setPanelExpansion(fraction);
}
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
@@ -504,7 +500,7 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -849,7 +845,7 @@
if (isShowing && isOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
- if (mCentralSurfaces.isInLaunchTransition()) {
+ if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
final Runnable endRunnable = new Runnable() {
@Override
public void run() {
@@ -903,7 +899,7 @@
} else {
mBouncerInteractor.startDisappearAnimation(finishRunnable);
}
- mCentralSurfaces.onBouncerPreHideAnimation();
+ mNotificationPanelViewController.startBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
@@ -935,7 +931,7 @@
long uptimeMillis = SystemClock.uptimeMillis();
long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
- if (mCentralSurfaces.isInLaunchTransition()
+ if (mNotificationPanelViewController.isLaunchTransitionFinished()
|| mKeyguardStateController.isFlingingToDismissKeyguard()) {
final boolean wasFlingingToDismissKeyguard =
mKeyguardStateController.isFlingingToDismissKeyguard();
@@ -1313,7 +1309,7 @@
@Override
public boolean shouldDisableWindowAnimationsForUnlock() {
- return mCentralSurfaces.isInLaunchTransition();
+ return mNotificationPanelViewController.isLaunchTransitionFinished();
}
@Override
@@ -1356,7 +1352,7 @@
mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
+ if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
resetAlternateAuth(false);
executeAfterKeyguardGoneAction();
}
@@ -1442,6 +1438,10 @@
pw.println(" mPendingWakeupAction: " + mPendingWakeupAction);
pw.println(" isBouncerShowing(): " + isBouncerShowing());
pw.println(" bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+ pw.println(" Registered KeyguardViewManagerCallbacks:");
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ pw.println(" " + callback);
+ }
if (mBouncer != null) {
mBouncer.dump(pw);
@@ -1466,6 +1466,20 @@
}
/**
+ * Add a callback to listen for changes
+ */
+ public void addCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ /**
+ * Removes callback to stop receiving updates
+ */
+ public void removeCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ /**
* Whether qs is currently expanded.
*/
public float getQsExpansion() {
@@ -1477,8 +1491,8 @@
*/
public void setQsExpansion(float qsExpansion) {
mQsExpansion = qsExpansion;
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setQsExpansion(qsExpansion);
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ callback.onQSExpansionChanged(mQsExpansion);
}
}
@@ -1492,21 +1506,13 @@
&& mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
}
- public boolean isShowingAlternateAuthOrAnimating() {
- return mAlternateAuthInterceptor != null
- && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
- || mAlternateAuthInterceptor.isAnimating());
- }
-
/**
- * Forward touches to any alternate authentication affordances.
+ * Forward touches to callbacks.
*/
- public boolean onTouch(MotionEvent event) {
- if (mAlternateAuthInterceptor == null) {
- return false;
+ public void onTouch(MotionEvent event) {
+ for (KeyguardViewManagerCallback callback: mCallbacks) {
+ callback.onTouch(event);
}
-
- return mAlternateAuthInterceptor.onTouch(event);
}
/** Update keyguard position based on a tapped X coordinate. */
@@ -1640,38 +1646,6 @@
boolean isShowingAlternateAuthBouncer();
/**
- * print information for the alternate auth interceptor registered
- */
- void dump(PrintWriter pw);
-
- /**
- * @return true if the new auth method bouncer is currently animating in or out.
- */
- boolean isAnimating();
-
- /**
- * How much QS is fully expanded where 0f is not showing and 1f is fully expanded.
- */
- void setQsExpansion(float qsExpansion);
-
- /**
- * Forward potential touches to authentication interceptor
- * @return true if event was handled
- */
- boolean onTouch(MotionEvent event);
-
- /**
- * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully
- * hidden
- */
- void setBouncerExpansionChanged(float expansion);
-
- /**
- * called when the bouncer view visibility has changed.
- */
- void onBouncerVisibilityChanged();
-
- /**
* Use when an app occluding the keyguard would like to give the user ability to
* unlock the device using udfps.
*
@@ -1680,5 +1654,25 @@
*/
void requestUdfps(boolean requestUdfps, int color);
+ /**
+ * print information for the alternate auth interceptor registered
+ */
+ void dump(PrintWriter pw);
+ }
+
+ /**
+ * Callback for KeyguardViewManager state changes.
+ */
+ public interface KeyguardViewManagerCallback {
+ /**
+ * Set the amount qs is expanded. For example, swipe down from the top of the
+ * lock screen to start the full QS expansion.
+ */
+ default void onQSExpansionChanged(float qsExpansion) { }
+
+ /**
+ * Forward touch events to callbacks
+ */
+ default void onTouch(MotionEvent event) { }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index ee948c0..b1642d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -31,7 +31,7 @@
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
- centralSurfaces.collapsePanelWithDuration(
+ centralSurfaces.notificationPanelViewController.collapseWithDuration(
ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 492734e..de7bf3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -212,7 +212,7 @@
private void updateWifiIconWithState(WifiIconState state) {
if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
if (state.visible && state.resId > 0) {
- mIconController.setSignalIcon(mSlotWifi, state);
+ mIconController.setWifiIcon(mSlotWifi, state);
mIconController.setIconVisibility(mSlotWifi, true);
} else {
mIconController.setIconVisibility(mSlotWifi, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 06cd12d..946d7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -27,11 +27,26 @@
/** True if we should display the mobile icons using the new status bar data pipeline. */
fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
+ /**
+ * True if we should run the new mobile icons backend to get the logging.
+ *
+ * Does *not* affect whether we render the mobile icons using the new backend data. See
+ * [useNewMobileIcons] for that.
+ */
+ fun runNewMobileIconsBackend(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
+
/** True if we should display the wifi icon using the new status bar data pipeline. */
fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
- // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
- // logging without getting the UI effects.
+ /**
+ * True if we should run the new wifi icon backend to get the logging.
+ *
+ * Does *not* affect whether we render the wifi icon using the new backend data. See
+ * [useNewWifiIcon] for that.
+ */
+ fun runNewWifiIconBackend(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
/**
* Returns true if we should apply some coloring to the wifi icon that was rendered with the new
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 380017c..c7e0ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import javax.inject.Inject
@@ -50,6 +51,7 @@
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
@Application scope: CoroutineScope,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
) {
private val mobileSubIds: Flow<List<Int>> =
interactor.filteredSubscriptions.mapLatest { infos ->
@@ -66,8 +68,14 @@
private val mobileSubIdsState: StateFlow<List<Int>> =
mobileSubIds
.onEach {
- // Notify the icon controller here so that it knows to add icons
- iconController.setNewMobileIconSubIds(it)
+ // Only notify the icon controller if we want to *render* the new icons.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+ // get the logging data without rendering.
+ if (statusBarPipelineFlags.useNewMobileIcons()) {
+ // Notify the icon controller here so that it knows to add icons
+ iconController.setNewMobileIconSubIds(it)
+ }
}
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
new file mode 100644
index 0000000..b816364
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * This class serves as a bridge between the old UI classes and the new data pipeline.
+ *
+ * Once the new pipeline notifies [wifiViewModel] that the wifi icon should be visible, this class
+ * notifies [iconController] to inflate the wifi icon (if needed). After that, the [wifiViewModel]
+ * has sole responsibility for updating the wifi icon drawable, visibility, etc. and the
+ * [iconController] will not do any updates to the icon.
+ */
+@SysUISingleton
+class WifiUiAdapter
+@Inject
+constructor(
+ private val iconController: StatusBarIconController,
+ private val wifiViewModel: WifiViewModel,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) {
+ /**
+ * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
+ * view once we receive a valid icon from the data pipeline.
+ *
+ * NOTE: This should go away as we better integrate the data pipeline with the UI.
+ *
+ * @return the view model used for this particular group in the given [location].
+ */
+ fun bindGroup(
+ statusBarIconGroup: ViewGroup,
+ location: StatusBarLocation,
+ ): LocationBasedWifiViewModel {
+ val locationViewModel =
+ when (location) {
+ StatusBarLocation.HOME -> wifiViewModel.home
+ StatusBarLocation.KEYGUARD -> wifiViewModel.keyguard
+ StatusBarLocation.QS -> wifiViewModel.qs
+ }
+
+ statusBarIconGroup.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ locationViewModel.wifiIcon.collect { wifiIcon ->
+ // Only notify the icon controller if we want to *render* the new icon.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
+ // want to get the logging data without rendering.
+ if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+ iconController.setNewWifiIcon()
+ }
+ }
+ }
+ }
+ }
+
+ return locationViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 25537b9..345f8cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,9 +30,7 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
@@ -62,26 +60,9 @@
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
}
- /**
- * Binds the view to the appropriate view-model based on the given location. The view will
- * continue to be updated following updates from the view-model.
- */
- @JvmStatic
- fun bind(
- view: ViewGroup,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
- ): Binding {
- return when (location) {
- StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
- StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
- StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
- }
- }
-
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
- private fun bind(
+ fun bind(
view: ViewGroup,
viewModel: LocationBasedWifiViewModel,
): Binding {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 0cd9bd7..a45076b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -26,9 +26,8 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
/**
* A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that
@@ -81,12 +80,11 @@
private fun initView(
slotName: String,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
+ wifiViewModel: LocationBasedWifiViewModel,
) {
slot = slotName
initDotView()
- binding = WifiViewBinder.bind(this, wifiViewModel, location)
+ binding = WifiViewBinder.bind(this, wifiViewModel)
}
// Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
@@ -116,14 +114,13 @@
fun constructAndBind(
context: Context,
slot: String,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
+ wifiViewModel: LocationBasedWifiViewModel,
): ModernStatusBarWifiView {
return (
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
- it.initView(slot, wifiViewModel, location)
+ it.initView(slot, wifiViewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 89b96b7..0782bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -145,7 +145,8 @@
else -> null
}
}
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+ .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index bd2123a..69b55c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -33,6 +33,7 @@
import androidx.annotation.NonNull;
import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -63,6 +64,7 @@
private volatile int mNumConnectedDevices;
// Assume tethering is available until told otherwise
private volatile boolean mIsTetheringSupported = true;
+ private final boolean mIsTetheringSupportedConfig;
private volatile boolean mHasTetherableWifiRegexs = true;
private boolean mWaitingForTerminalState;
@@ -100,23 +102,29 @@
mTetheringManager = context.getSystemService(TetheringManager.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mMainHandler = mainHandler;
- mTetheringManager.registerTetheringEventCallback(
- new HandlerExecutor(backgroundHandler), mTetheringCallback);
+ mIsTetheringSupportedConfig = context.getResources()
+ .getBoolean(R.bool.config_show_wifi_tethering);
+ if (mIsTetheringSupportedConfig) {
+ mTetheringManager.registerTetheringEventCallback(
+ new HandlerExecutor(backgroundHandler), mTetheringCallback);
+ }
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
/**
* Whether hotspot is currently supported.
*
- * This will return {@code true} immediately on creation of the controller, but may be updated
- * later. Callbacks from this controllers will notify if the state changes.
+ * This may return {@code true} immediately on creation of the controller, but may be updated
+ * later as capabilities are collected from System Server.
+ *
+ * Callbacks from this controllers will notify if the state changes.
*
* @return {@code true} if hotspot is supported (or we haven't been told it's not)
* @see #addCallback
*/
@Override
public boolean isHotspotSupported() {
- return mIsTetheringSupported && mHasTetherableWifiRegexs
+ return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
&& UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index 07e5cf9..2080614 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -28,6 +28,8 @@
import android.view.WindowManagerGlobal
import android.widget.Toast
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -58,6 +60,8 @@
private val devicePolicyManager: DevicePolicyManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
private val uiEventLogger: UiEventLogger,
+ resumeSessionReceiver: GuestResumeSessionReceiver,
+ resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver,
) {
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
@@ -65,6 +69,11 @@
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+ init {
+ resumeSessionReceiver.register()
+ resetOrExitSessionReceiver.register()
+ }
+
/** Notifies that the device has finished booting. */
fun onDeviceBootCompleted() {
applicationScope.launch {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c0..131cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@
keyguardOccluded = false,
occludingAppRequestingFp = false,
primaryUser = false,
+ shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 52f8ef8..0a2b3d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -194,6 +194,15 @@
}
@Test
+ public void onInitConfiguresViewMode() {
+ mKeyguardSecurityContainerController.onInit();
+ verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
+ }
+
+ @Test
public void showSecurityScreen_canInflateAllModes() {
SecurityMode[] modes = SecurityMode.values();
for (SecurityMode mode : modes) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5104f84..a8284d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -20,6 +20,7 @@
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -38,6 +39,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -53,6 +55,7 @@
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -60,18 +63,21 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -110,6 +116,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -181,6 +188,8 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private SecureSettings mSecureSettings;
+ @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -214,6 +223,7 @@
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -223,6 +233,9 @@
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+ @Mock
+ private Uri mURI;
+
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+
+ when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ ExtendedMockito.spyOn(contentResolver);
+ doNothing().when(contentResolver)
+ .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+ anyInt());
+
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
@@ -1136,6 +1158,64 @@
}
@Test
+ public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+ throws RemoteException {
+ // SFPS supported and enrolled
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+ // WHEN require screen on to auth is disabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+ // Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+
+ statusBarShadeIsLocked();
+ mTestableLooper.processAllMessages();
+
+ // THEN we should listen for sfps when screen off, because require screen on is disabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+ // WHEN require screen on to auth is enabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+ // Device now awake & keyguard is now interactive
+ deviceNotGoingToSleep();
+ deviceIsInteractive();
+ keyguardIsVisible();
+
+ // THEN we should listen for sfps when screen on, and require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
+
+
+ private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType) {
+ return new FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ new ArrayList<ComponentInfoInternal>(),
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
+ @Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1804,7 +1884,7 @@
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mDumpManager,
+ mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e8c760c..d1107c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,6 +35,8 @@
import android.view.WindowManager
import android.widget.ScrollView
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
@@ -159,6 +161,35 @@
}
@Test
+ fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
+ val container = initializeFingerprintContainer(
+ authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ waitForIdleSync()
+
+ val requestID = authContainer?.requestId ?: 0L
+
+ // Simulate keyboard was shown on the credential view
+ val windowInsetsController = container.windowInsetsController
+ spyOn(windowInsetsController)
+ spyOn(container.rootWindowInsets)
+ doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime())
+
+ container.onWindowFocusChanged(false)
+ waitForIdleSync()
+
+ // Expect hiding IME request will be invoked when dismissing the view
+ verify(windowInsetsController)?.hide(WindowInsets.Type.ime())
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(requestID)
+ )
+ assertThat(container.parent).isNull()
+ }
+
+ @Test
fun testActionAuthenticated_sendsDismissedAuthenticated() {
val container = initializeFingerprintContainer()
container.mBiometricCallback.onAction(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index a02dfa3..a275c8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -87,6 +87,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
@@ -173,6 +174,9 @@
private DelayableExecutor mBackgroundExecutor;
private TestableAuthController mAuthController;
+ @Mock
+ private VibratorHelper mVibratorHelper;
+
@Before
public void setup() throws RemoteException {
mContextSpy = spy(mContext);
@@ -221,7 +225,8 @@
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
- () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController);
+ () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController,
+ mVibratorHelper);
mAuthController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -246,11 +251,13 @@
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ when(mVibratorHelper.hasVibrator()).thenReturn(true);
+
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
- mStatusBarStateController);
+ mStatusBarStateController, mVibratorHelper);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -270,11 +277,13 @@
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ when(mVibratorHelper.hasVibrator()).thenReturn(true);
+
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
- mStatusBarStateController);
+ mStatusBarStateController, mVibratorHelper);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -928,12 +937,13 @@
FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
Provider<SidefpsController> sidefpsControllerFactory,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ VibratorHelper vibratorHelper) {
super(context, execution, commandQueue, activityTaskManager, windowManager,
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
mUserManager, mLockPatternUtils, statusBarStateController,
- mInteractionJankMonitor, mHandler, mBackgroundExecutor);
+ mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index c85334d..90948ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -42,6 +42,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -103,6 +105,8 @@
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var bouncerInteractor: BouncerInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -136,7 +140,8 @@
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
+ controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
+ bouncerInteractor, isDebuggable
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 28e13b8..be39c0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -69,7 +69,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -171,6 +173,8 @@
private FakeExecutor mFgExecutor;
@Mock
private UdfpsDisplayMode mUdfpsDisplayMode;
+ @Mock
+ private FeatureFlags mFeatureFlags;
// Stuff for configuring mocks
@Mock
@@ -191,6 +195,8 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+ @Mock
+ private BouncerInteractor mBouncerInteractor;
// Capture listeners so that they can be used to send events
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
@@ -252,6 +258,7 @@
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
+ mFeatureFlags,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
@@ -270,7 +277,8 @@
mLatencyTracker,
mActivityLaunchAnimator,
Optional.of(mAlternateTouchProvider),
- mBiometricsExecutor);
+ mBiometricsExecutor,
+ mBouncerInteractor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
new file mode 100644
index 0000000..e5c7a42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
+ // Dependencies
+ protected @Mock UdfpsKeyguardView mView;
+ protected @Mock Context mResourceContext;
+ protected @Mock StatusBarStateController mStatusBarStateController;
+ protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+ protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ protected @Mock DumpManager mDumpManager;
+ protected @Mock DelayableExecutor mExecutor;
+ protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ protected @Mock KeyguardStateController mKeyguardStateController;
+ protected @Mock KeyguardViewMediator mKeyguardViewMediator;
+ protected @Mock ConfigurationController mConfigurationController;
+ protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ protected @Mock SystemUIDialogManager mDialogManager;
+ protected @Mock UdfpsController mUdfpsController;
+ protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ protected @Mock KeyguardBouncer mBouncer;
+ protected @Mock BouncerInteractor mBouncerInteractor;
+
+ protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ protected FakeSystemClock mSystemClock = new FakeSystemClock();
+
+ protected UdfpsKeyguardViewController mController;
+
+ // Capture listeners so that they can be used to send events
+ private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+ protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+ private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+ protected List<ShadeExpansionListener> mExpansionListeners;
+
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
+ mAltAuthInterceptorCaptor;
+ protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+
+ private @Captor ArgumentCaptor<KeyguardStateController.Callback>
+ mKeyguardStateControllerCallbackCaptor;
+ protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mView.getContext()).thenReturn(mResourceContext);
+ when(mResourceContext.getString(anyInt())).thenReturn("test string");
+ when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ mController = createUdfpsKeyguardViewController();
+ }
+
+ protected void sendStatusBarStateChanged(int statusBarState) {
+ mStatusBarStateListener.onStateChanged(statusBarState);
+ }
+
+ protected void captureStatusBarStateListeners() {
+ verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+ mStatusBarStateListener = mStateListenerCaptor.getValue();
+ }
+
+ protected void captureStatusBarExpansionListeners() {
+ verify(mShadeExpansionStateManager, times(2))
+ .addExpansionListener(mExpansionListenerCaptor.capture());
+ // first (index=0) is from super class, UdfpsAnimationViewController.
+ // second (index=1) is from UdfpsKeyguardViewController
+ mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+ }
+
+ protected void updateStatusBarExpansion(float fraction, boolean expanded) {
+ ShadeExpansionChangeEvent event =
+ new ShadeExpansionChangeEvent(
+ fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
+ for (ShadeExpansionListener listener : mExpansionListeners) {
+ listener.onPanelExpansionChanged(event);
+ }
+ }
+
+ protected void captureAltAuthInterceptor() {
+ verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
+ mAltAuthInterceptorCaptor.capture());
+ mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+ }
+
+ protected void captureKeyguardStateControllerCallback() {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateControllerCallbackCaptor.capture());
+ mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ }
+
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(false);
+ }
+
+ protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
+ boolean useModernBouncer) {
+ mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+ when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+ useModernBouncer ? null : mBouncer);
+ return new UdfpsKeyguardViewController(
+ mView,
+ mStatusBarStateController,
+ mShadeExpansionStateManager,
+ mStatusBarKeyguardViewManager,
+ mKeyguardUpdateMonitor,
+ mDumpManager,
+ mLockscreenShadeTransitionController,
+ mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
+ mDialogManager,
+ mUdfpsController,
+ mActivityLaunchAnimator,
+ mFeatureFlags,
+ mBouncerInteractor);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index c0f9c82..55b6194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -25,126 +25,53 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
-public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
- // Dependencies
- @Mock
- private UdfpsKeyguardView mView;
- @Mock
- private Context mResourceContext;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private ShadeExpansionStateManager mShadeExpansionStateManager;
- @Mock
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock
- private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- @Mock
- private DumpManager mDumpManager;
- @Mock
- private DelayableExecutor mExecutor;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- @Mock
- private SystemUIDialogManager mDialogManager;
- @Mock
- private UdfpsController mUdfpsController;
- @Mock
- private ActivityLaunchAnimator mActivityLaunchAnimator;
- private FakeSystemClock mSystemClock = new FakeSystemClock();
+public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
+ private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+ mBouncerExpansionCallbackCaptor;
+ private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
- private UdfpsKeyguardViewController mController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
- private List<ShadeExpansionListener> mExpansionListeners;
-
- @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
- mAltAuthInterceptorCaptor;
- private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
-
- @Captor private ArgumentCaptor<KeyguardStateController.Callback>
- mKeyguardStateControllerCallbackCaptor;
- private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mView.getContext()).thenReturn(mResourceContext);
- when(mResourceContext.getString(anyInt())).thenReturn("test string");
- when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
- when(mView.getUnpausedAlpha()).thenReturn(255);
- mController = new UdfpsKeyguardViewController(
- mView,
- mStatusBarStateController,
- mShadeExpansionStateManager,
- mStatusBarKeyguardViewManager,
- mKeyguardUpdateMonitor,
- mDumpManager,
- mLockscreenShadeTransitionController,
- mConfigurationController,
- mSystemClock,
- mKeyguardStateController,
- mUnlockedScreenOffAnimationController,
- mDialogManager,
- mUdfpsController,
- mActivityLaunchAnimator);
+ @Override
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
}
@Test
+ public void testShouldPauseAuth_bouncerShowing() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ captureBouncerExpansionCallback();
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+
+
+ @Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
captureStatusBarExpansionListeners();
@@ -202,20 +129,6 @@
}
@Test
- public void testShouldPauseAuthBouncerShowing() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- captureAltAuthInterceptor();
- when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
- mAltAuthInterceptor.onBouncerVisibilityChanged();
-
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
public void testShouldPauseAuthUnpausedAlpha0() {
mController.onViewAttached();
captureStatusBarStateListeners();
@@ -503,41 +416,8 @@
verify(mView, atLeastOnce()).setPauseAuth(false);
}
- private void sendStatusBarStateChanged(int statusBarState) {
- mStatusBarStateListener.onStateChanged(statusBarState);
- }
-
- private void captureStatusBarStateListeners() {
- verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
- mStatusBarStateListener = mStateListenerCaptor.getValue();
- }
-
- private void captureStatusBarExpansionListeners() {
- verify(mShadeExpansionStateManager, times(2))
- .addExpansionListener(mExpansionListenerCaptor.capture());
- // first (index=0) is from super class, UdfpsAnimationViewController.
- // second (index=1) is from UdfpsKeyguardViewController
- mExpansionListeners = mExpansionListenerCaptor.getAllValues();
- }
-
- private void updateStatusBarExpansion(float fraction, boolean expanded) {
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
- for (ShadeExpansionListener listener : mExpansionListeners) {
- listener.onPanelExpansionChanged(event);
- }
- }
-
- private void captureAltAuthInterceptor() {
- verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
- mAltAuthInterceptorCaptor.capture());
- mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
- }
-
- private void captureKeyguardStateControllerCallback() {
- verify(mKeyguardStateController).addCallback(
- mKeyguardStateControllerCallbackCaptor.capture());
- mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ private void captureBouncerExpansionCallback() {
+ verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
+ mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..7b19768
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
+ lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+ @Before
+ override fun setUp() {
+ allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
+ MockitoAnnotations.initMocks(this)
+ keyguardBouncerRepository =
+ KeyguardBouncerRepository(
+ mock(com.android.keyguard.ViewMediatorCallback::class.java),
+ mKeyguardUpdateMonitor
+ )
+ super.setUp()
+ }
+
+ override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
+ mBouncerInteractor =
+ BouncerInteractor(
+ keyguardBouncerRepository,
+ mock(BouncerView::class.java),
+ mock(Handler::class.java),
+ mKeyguardStateController,
+ mock(KeyguardSecurityModel::class.java),
+ mock(BouncerCallbackInteractor::class.java),
+ mock(FalsingCollector::class.java),
+ mock(DismissCallbackRegistry::class.java),
+ mock(KeyguardBypassController::class.java),
+ mKeyguardUpdateMonitor
+ )
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testShouldPauseAuthBouncerShowing() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN view attached and we're on the keyguard
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+ // WHEN the bouncer expansion is VISIBLE
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setVisible(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ yield()
+
+ // THEN UDFPS shouldPauseAuth == true
+ assertTrue(mController.shouldPauseAuth())
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c5a7de4..c234178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -92,6 +92,12 @@
@Mock
BouncerCallbackInteractor mBouncerCallbackInteractor;
+ @Mock
+ DreamOverlayAnimationsController mAnimationsController;
+
+ @Mock
+ DreamOverlayStateController mStateController;
+
DreamOverlayContainerViewController mController;
@Before
@@ -115,7 +121,9 @@
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL,
MILLIS_UNTIL_FULL_JITTER,
- mBouncerCallbackInteractor);
+ mBouncerCallbackInteractor,
+ mAnimationsController,
+ mStateController);
}
@Test
@@ -188,4 +196,31 @@
verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction);
verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false);
}
+
+ @Test
+ public void testStartDreamEntryAnimationsOnAttachedNonLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(false);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+ verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+ }
+
+ @Test
+ public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(true);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+ }
+
+ @Test
+ public void testCancelDreamEntryAnimationsOnDetached() {
+ mController.onViewAttached();
+ mController.onViewDetached();
+
+ verify(mAnimationsController).cancelRunningEntryAnimations();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f370be1..b7f694f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -253,6 +253,7 @@
verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
verify(mStateController).setOverlayActive(false);
verify(mStateController).setLowLightActive(false);
+ verify(mStateController).setEntryAnimationsFinished(false);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index d1d32a1..c21c7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -234,4 +234,20 @@
verify(mCallback, times(1)).onStateChanged();
assertThat(stateController.isLowLightActive()).isTrue();
}
+
+ @Test
+ public void testNotifyEntryAnimationsFinishedChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.areEntryAnimationsFinished()).isFalse();
+
+ stateController.setEntryAnimationsFinished(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.areEntryAnimationsFinished()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index aa02178..85c2819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -19,16 +19,20 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
+import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.net.ConnectivityManager;
@@ -55,6 +59,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -69,7 +74,7 @@
"{count, plural, =1 {# notification} other {# notifications}}";
@Mock
- DreamOverlayStatusBarView mView;
+ MockDreamOverlayStatusBarView mView;
@Mock
ConnectivityManager mConnectivityManager;
@Mock
@@ -105,6 +110,9 @@
@Mock
DreamOverlayStateController mDreamOverlayStateController;
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
private final Executor mMainExecutor = Runnable::run;
DreamOverlayStatusBarViewController mController;
@@ -115,6 +123,8 @@
when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator))
.thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
+ doCallRealMethod().when(mView).setVisibility(anyInt());
+ doCallRealMethod().when(mView).getVisibility();
mController = new DreamOverlayStatusBarViewController(
mView,
@@ -454,12 +464,10 @@
public void testStatusBarHiddenWhenSystemStatusBarShown() {
mController.onViewAttached();
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(true);
- verify(mView).setVisibility(View.INVISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
@@ -467,29 +475,43 @@
mController.onViewAttached();
reset(mView);
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(false);
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
mController.onViewAttached();
+ updateEntryAnimationsFinished();
mController.onViewDetached();
reset(mView);
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateStatusBarWindowState(true);
verify(mView, never()).setVisibility(anyInt());
}
@Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenStatusBarHidden() {
+ mController.onViewAttached();
+
+ // Trigger status bar window state change.
+ final StatusBarWindowStateListener listener = updateStatusBarWindowState(false);
+
+ // Verify no visibility change because dream not started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger another status bar window state change, and verify visibility change.
+ listener.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void testExtraStatusBarItemSetWhenItemsChange() {
mController.onViewAttached();
when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView);
@@ -507,16 +529,75 @@
public void testLowLightHidesStatusBar() {
when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
mController.onViewAttached();
+ updateEntryAnimationsFinished();
- verify(mView).setVisibility(View.INVISIBLE);
- reset(mView);
-
- when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
callbackCapture.getValue().onStateChanged();
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
+ reset(mView);
+
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ callbackCapture.getValue().onStateChanged();
+
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenLowLightStateChange() {
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ mController.onViewAttached();
+
+ // No change to visibility because dream not fully started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger state change and verify visibility changed.
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
+
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
+ when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
+ final ArgumentCaptor<StatusBarWindowStateListener>
+ callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+ verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
+ final StatusBarWindowStateListener listener = callbackCapture.getValue();
+ listener.onStatusBarWindowStateChanged(show ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN);
+ return listener;
+ }
+
+ private void updateEntryAnimationsFinished() {
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ final DreamOverlayStateController.Callback callback = mCallbackCaptor.getValue();
+ callback.onStateChanged();
+ }
+
+ private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView {
+ private int mVisibility = View.VISIBLE;
+
+ private MockDreamOverlayStatusBarView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mVisibility = visibility;
+ }
+
+ @Override
+ public int getVisibility() {
+ return mVisibility;
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index 3b9e398..b477592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.dreams.complication;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -29,16 +30,18 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
@SmallTest
@@ -77,9 +80,20 @@
@Mock
ComplicationLayoutParams mComplicationLayoutParams;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ @Captor
+ private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor;
+
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
@Complication.Category
static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
+ private ComplicationHostViewController mController;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -91,6 +105,15 @@
when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+
+ mController = new ComplicationHostViewController(
+ mComplicationHostView,
+ mLayoutEngine,
+ mDreamOverlayStateController,
+ mLifecycleOwner,
+ mViewModel);
+
+ mController.init();
}
/**
@@ -98,25 +121,12 @@
*/
@Test
public void testViewModelObservation() {
- final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
- ArgumentCaptor.forClass(Observer.class);
- final ComplicationHostViewController controller = new ComplicationHostViewController(
- mComplicationHostView,
- mLayoutEngine,
- mLifecycleOwner,
- mViewModel);
-
- controller.init();
-
- verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
- observerArgumentCaptor.capture());
-
final Observer<Collection<ComplicationViewModel>> observer =
- observerArgumentCaptor.getValue();
+ captureComplicationViewModelsObserver();
- // Add complication and ensure it is added to the view.
+ // Add a complication and ensure it is added to the view.
final HashSet<ComplicationViewModel> complications = new HashSet<>(
- Arrays.asList(mComplicationViewModel));
+ Collections.singletonList(mComplicationViewModel));
observer.onChanged(complications);
verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
@@ -127,4 +137,48 @@
verify(mLayoutEngine).removeComplication(eq(mComplicationId));
}
+
+ @Test
+ public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Add a complication before entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should be set to invisible.
+ verify(mComplicationView).setVisibility(View.INVISIBLE);
+ }
+
+ @Test
+ public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() {
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Dream entry animations finished.
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+ final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback();
+ stateCallback.onStateChanged();
+
+ // Add a complication after entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should not be set to invisible.
+ verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+ }
+
+ private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
+ verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+ mObserverCaptor.capture());
+ return mObserverCaptor.getValue();
+ }
+
+ private DreamOverlayStateController.Callback captureOverlayStateCallback() {
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ return mCallbackCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index f18acba..0fb181d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -23,14 +23,11 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig(
+/** Fake implementation of a quick affordance data source. */
+class FakeKeyguardQuickAffordanceConfig(
override val key: String,
+ override val pickerName: String = key,
+ override val pickerIconResourceId: Int = 0,
) : KeyguardQuickAffordanceConfig {
var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
new file mode 100644
index 0000000..d2422ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardQuickAffordanceSelectionManager()
+ }
+
+ @Test
+ fun setSelections() =
+ runBlocking(IMMEDIATE) {
+ var affordanceIdsBySlotId: Map<String, List<String>>? = null
+ val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ ),
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(affordanceId2),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1, affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ private suspend fun assertSelections(
+ observed: Map<String, List<String>>?,
+ expected: Map<String, List<String>>,
+ ) {
+ assertThat(underTest.getSelections()).isEqualTo(expected)
+ assertThat(observed).isEqualTo(expected)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 61a3f9f..2bd8e9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -50,7 +50,7 @@
MockitoAnnotations.initMocks(this)
whenever(controller.intent).thenReturn(INTENT_1)
- underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+ underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index c05beef..5178154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -59,6 +59,7 @@
underTest =
QuickAccessWalletKeyguardQuickAffordanceConfig(
+ mock(),
walletController,
activityStarter,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
new file mode 100644
index 0000000..5a7f2bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+ private lateinit var config1: FakeKeyguardQuickAffordanceConfig
+ private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
+ config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ underTest =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(config1, config2),
+ )
+ }
+
+ @Test
+ fun setSelections() =
+ runBlocking(IMMEDIATE) {
+ var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
+ val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+
+ underTest.setSelections(slotId1, listOf(config1.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to listOf(config1),
+ ),
+ )
+
+ underTest.setSelections(slotId2, listOf(config2.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to listOf(config1),
+ slotId2 to listOf(config2),
+ ),
+ )
+
+ underTest.setSelections(slotId1, emptyList())
+ underTest.setSelections(slotId2, listOf(config1.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to emptyList(),
+ slotId2 to listOf(config1),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun getAffordancePickerRepresentations() {
+ assertThat(underTest.getAffordancePickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config1.key,
+ name = config1.pickerName,
+ iconResourceId = config1.pickerIconResourceId,
+ ),
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config2.key,
+ name = config2.pickerName,
+ iconResourceId = config2.pickerIconResourceId,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun getSlotPickerRepresentations() {
+ assertThat(underTest.getSlotPickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ maxSelectedAffordances = 1,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ maxSelectedAffordances = 1,
+ ),
+ )
+ )
+ }
+
+ private suspend fun assertSelections(
+ observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
+ expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
+ ) {
+ assertThat(observed).isEqualTo(expected)
+ assertThat(underTest.getSelections())
+ .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
+ expected.forEach { (slotId, configs) ->
+ assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+ }
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9ab9d3..53d9b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -21,8 +21,10 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -46,6 +48,7 @@
@Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -59,6 +62,7 @@
keyguardStateController,
dozeHost,
wakefulnessLifecycle,
+ biometricUnlockController,
)
}
@@ -190,7 +194,7 @@
}
@Test
- fun wakefullness() = runBlockingTest {
+ fun wakefulness() = runBlockingTest {
val values = mutableListOf<WakefulnessModel>()
val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
@@ -217,4 +221,63 @@
job.cancel()
verify(wakefulnessLifecycle).removeObserver(captor.value)
}
+
+ @Test
+ fun isBouncerShowing() = runBlockingTest {
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun biometricUnlockState() = runBlockingTest {
+ val values = mutableListOf<BiometricUnlockModel>()
+ val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+ verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+ captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be NONE, followed by onModeChanged() call
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.SHOW_BOUNCER,
+ BiometricUnlockModel.ONLY_WAKE,
+ BiometricUnlockModel.UNLOCK_COLLAPSING,
+ BiometricUnlockModel.DISMISS_BOUNCER,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ )
+ )
+
+ job.cancel()
+ verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 64913c7c..27d5d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -128,11 +128,15 @@
assertThat(steps.size).isEqualTo(3)
assertThat(steps[0])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
assertThat(steps[1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME)
+ )
assertThat(steps[2])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+ )
job.cancel()
}
@@ -174,15 +178,22 @@
}
private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
- assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ assertThat(steps[0])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
fractions.forEachIndexed { index, fraction ->
assertThat(steps[index + 1])
.isEqualTo(
- TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+ TransitionStep(
+ AOD,
+ LOCKSCREEN,
+ fraction.toFloat(),
+ TransitionState.RUNNING,
+ OWNER_NAME
+ )
)
}
assertThat(steps[steps.size - 1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
assertThat(wtfHandler.failed).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
index e6c8dd8..5743b2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -27,8 +27,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -57,6 +57,7 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@@ -86,6 +87,7 @@
)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
`when`(repository.show.value).thenReturn(null)
+ `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
}
@Test
@@ -97,7 +99,7 @@
verify(repository).setHide(false)
verify(repository).setStartingToHide(false)
verify(repository).setScrimmed(true)
- verify(repository).setExpansion(EXPANSION_VISIBLE)
+ verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
verify(repository).setShowingSoon(true)
verify(keyguardStateController).notifyBouncerShowing(true)
verify(bouncerCallbackInteractor).dispatchStartingToShow()
@@ -108,7 +110,7 @@
@Test
fun testShow_isNotScrimmed() {
- verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+ verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
}
@Test
@@ -124,7 +126,6 @@
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setShowingSoon(false)
- verify(repository).setOnDismissAction(null)
verify(repository).setVisible(false)
verify(repository).setHide(true)
verify(repository).setShow(null)
@@ -132,26 +133,26 @@
@Test
fun testExpansion() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
- bouncerInteractor.setExpansion(0.6f)
- verify(repository).setExpansion(0.6f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ bouncerInteractor.setPanelExpansion(0.6f)
+ verify(repository).setPanelExpansion(0.6f)
verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@Test
fun testExpansion_fullyShown() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(bouncerCallbackInteractor).dispatchFullyShown()
}
@Test
fun testExpansion_fullyHidden() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+ bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setVisible(false)
verify(repository).setShow(null)
verify(falsingCollector).onBouncerHidden()
@@ -161,8 +162,8 @@
@Test
fun testExpansion_startingToHide() {
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- bouncerInteractor.setExpansion(0.1f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(0.1f)
verify(repository).setStartingToHide(true)
verify(bouncerCallbackInteractor).dispatchStartingToHide()
}
@@ -178,8 +179,7 @@
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
- verify(repository)
- .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
@@ -234,7 +234,7 @@
@Test
fun testIsFullShowing() {
`when`(repository.isVisible.value).thenReturn(true)
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
assertThat(bouncerInteractor.isFullyShowing()).isTrue()
`when`(repository.isVisible.value).thenReturn(false)
@@ -255,7 +255,7 @@
assertThat(bouncerInteractor.isInTransit()).isTrue()
`when`(repository.showingSoon.value).thenReturn(false)
assertThat(bouncerInteractor.isInTransit()).isFalse()
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
assertThat(bouncerInteractor.isInTransit()).isTrue()
}
@@ -269,10 +269,9 @@
@Test
fun testWillDismissWithAction() {
- `when`(repository.onDismissAction.value?.onDismissAction)
- .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
- `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 7116cc1..8b6603d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,10 +25,14 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
@@ -37,6 +41,8 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
@@ -189,6 +195,8 @@
/* startActivity= */ true,
),
)
+
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -213,10 +221,20 @@
whenever(expandable.activityLaunchController()).thenReturn(animationController)
homeControls =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+ val quickAccessWallet =
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ val qrCodeScanner =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ )
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -229,14 +247,8 @@
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {},
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {},
+ quickAccessWallet,
+ qrCodeScanner,
),
),
),
@@ -244,6 +256,11 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ },
+ repository = { quickAffordanceRepository },
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index ae32ba6..3364535 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,22 +22,31 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.yield
import org.junit.Before
@@ -47,6 +56,7 @@
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@@ -62,6 +72,7 @@
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
@@ -71,20 +82,25 @@
repository.setKeyguardShowing(true)
homeControls =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWallet =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {}
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
qrCodeScanner =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ )
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ }
underTest =
KeyguardQuickAffordanceInteractor(
@@ -107,6 +123,8 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags = featureFlags,
+ repository = { quickAffordanceRepository },
)
}
@@ -210,6 +228,270 @@
job.cancel()
}
+ @Test
+ fun select() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ var startConfig: KeyguardQuickAffordanceModel? = null
+ val job1 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { startConfig = it }
+ .launchIn(this)
+ var endConfig: KeyguardQuickAffordanceModel? = null
+ val job2 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { endConfig = it }
+ .launchIn(this)
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${homeControls.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(homeControls.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ underTest.select(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ quickAccessWallet.key
+ )
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(quickAccessWallet.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+ "::${qrCodeScanner.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(quickAccessWallet.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(qrCodeScanner.key),
+ )
+ )
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun `unselect - one`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ var startConfig: KeyguardQuickAffordanceModel? = null
+ val job1 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { startConfig = it }
+ .launchIn(this)
+ var endConfig: KeyguardQuickAffordanceModel? = null
+ val job2 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { endConfig = it }
+ .launchIn(this)
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+ yield()
+ yield()
+
+ underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(quickAccessWallet.key),
+ )
+ )
+
+ underTest.unselect(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ quickAccessWallet.key
+ )
+ yield()
+ yield()
+
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun `unselect - all`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+ yield()
+ yield()
+
+ underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
+ yield()
+ yield()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(quickAccessWallet.key),
+ )
+ )
+
+ underTest.unselect(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ null,
+ )
+ yield()
+ yield()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+ }
+
companion object {
private val ICON: Icon = mock {
whenever(this.contentDescription)
@@ -220,5 +502,6 @@
)
}
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0424c28..6333b24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -54,9 +54,11 @@
@Test
fun `transition collectors receives only appropriate events`() =
runBlocking(IMMEDIATE) {
- var goneToAodSteps = mutableListOf<TransitionStep>()
+ var lockscreenToAodSteps = mutableListOf<TransitionStep>()
val job1 =
- underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this)
+ underTest.lockscreenToAodTransition
+ .onEach { lockscreenToAodSteps.add(it) }
+ .launchIn(this)
var aodToLockscreenSteps = mutableListOf<TransitionStep>()
val job2 =
@@ -70,14 +72,14 @@
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
- steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
- steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
steps.forEach { repository.sendTransitionStep(it) }
assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
- assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8))
+ assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
job1.cancel()
job2.cancel()
@@ -119,10 +121,8 @@
fun keyguardStateTests() =
runBlocking(IMMEDIATE) {
var finishedSteps = mutableListOf<KeyguardState>()
- val job1 =
+ val job =
underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
- var startedSteps = mutableListOf<KeyguardState>()
- val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -137,10 +137,60 @@
steps.forEach { repository.sendTransitionStep(it) }
assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
- assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE))
- job1.cancel()
- job2.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun finishedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var finishedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.finishedKeyguardTransitionStep
+ .onEach { finishedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+
+ job.cancel()
+ }
+
+ @Test
+ fun startedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var startedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.startedKeyguardTransitionStep
+ .onEach { startedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+
+ job.cancel()
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index f73d1ec..78148c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,10 +23,14 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -41,6 +45,8 @@
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -82,20 +88,13 @@
.thenReturn(RETURNED_BURN_IN_OFFSET)
homeControlsQuickAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWalletAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {}
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
qrCodeScannerAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -116,6 +115,18 @@
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs =
+ setOf(
+ homeControlsQuickAffordanceConfig,
+ quickAccessWalletAffordanceConfig,
+ qrCodeScannerAffordanceConfig,
+ ),
+ )
underTest =
KeyguardBottomAreaViewModel(
keyguardInteractor = keyguardInteractor,
@@ -127,6 +138,11 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ },
+ repository = { quickAffordanceRepository },
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
@@ -576,5 +592,6 @@
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 6adce7a..c1fa9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -61,6 +61,7 @@
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -201,6 +202,8 @@
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private Resources mResources;
+ @Mock
+ private ViewRootImpl mViewRootImpl;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -227,6 +230,7 @@
when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
.thenReturn(mContext);
when(mNavigationBarView.getResources()).thenReturn(mResources);
+ when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2c76be6..b067ee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -136,6 +137,20 @@
assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
}
+ @Test
+ public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+ ImageView iv = new ImageView(mContext);
+ AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getDrawable(any())).thenReturn(d);
+ when(s.icon.getInvisibleDrawable(any())).thenReturn(d);
+
+ mIconView.updateIcon(iv, s, false);
+
+ verify(d, never()).start();
+ }
+
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
return new Drawable.ConstantState() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 45b4353..c98c1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -486,6 +486,7 @@
mSysUiState,
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
+ mKeyguardIndicationController,
mNotificationListContainer,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
@@ -499,8 +500,6 @@
() -> {},
mNotificationShelfController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
verify(mView, atLeast(1)).addOnAttachStateChangeListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 26a0770..a4a7995 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -152,7 +152,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should intercept touch
@@ -165,7 +165,7 @@
// WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we shouldn't intercept touch
@@ -178,7 +178,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 09add65..43c6942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -66,6 +66,8 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var shadeLogger: ShadeLogger
private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
private lateinit var underTest: PulsingGestureListener
@@ -81,6 +83,7 @@
centralSurfaces,
ambientDisplayConfiguration,
statusBarStateController,
+ shadeLogger,
tunerService,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 6fa2174..b17747a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -143,7 +143,7 @@
mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
- mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+ mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5755782..7ce3a67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -500,7 +500,7 @@
mKeyguardVieMediatorCallback);
// TODO: we should be able to call mCentralSurfaces.start() and have all the below values
- // initialized automatically.
+ // initialized automatically and make NPVC private.
mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController;
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 9c56c26..6fb6893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -45,7 +45,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -80,7 +80,7 @@
layout,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- mock(WifiViewModel.class),
+ mock(WifiUiAdapter.class),
mock(MobileUiAdapter.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
@@ -124,14 +124,14 @@
LinearLayout group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
contextProvider,
darkIconDispatcher);
@@ -172,7 +172,7 @@
super(group,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- mock(WifiViewModel.class),
+ mock(WifiUiAdapter.class),
mock(MobileUiAdapter.class),
contextProvider);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0c35659..7166666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -307,7 +307,7 @@
@Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+ when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
expansionEvent(
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
@@ -361,7 +361,7 @@
@Test
public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+ when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
mStatusBarKeyguardViewManager.show(null);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index c584109..37457b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -40,6 +39,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -70,7 +70,7 @@
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
- private lateinit var viewModel: WifiViewModel
+ private lateinit var viewModel: LocationBasedWifiViewModel
private lateinit var scope: CoroutineScope
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
@@ -105,23 +105,19 @@
scope,
statusBarPipelineFlags,
wifiConstants,
- )
+ ).home
}
@Test
fun constructAndBind_hasCorrectSlot() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, "slotName", viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel)
assertThat(view.slot).isEqualTo("slotName")
}
@Test
fun getVisibleState_icon_returnsIcon() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_ICON, /* animate= */ false)
@@ -130,9 +126,7 @@
@Test
fun getVisibleState_dot_returnsDot() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_DOT, /* animate= */ false)
@@ -141,9 +135,7 @@
@Test
fun getVisibleState_hidden_returnsHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
@@ -155,9 +147,7 @@
@Test
fun setVisibleState_icon_iconShownDotHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_ICON, /* animate= */ false)
@@ -172,9 +162,7 @@
@Test
fun setVisibleState_dot_iconHiddenDotShown() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_DOT, /* animate= */ false)
@@ -189,9 +177,7 @@
@Test
fun setVisibleState_hidden_iconAndDotHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
@@ -211,9 +197,7 @@
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -230,9 +214,7 @@
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 4f1fb02..26df03f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +26,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.net.TetheringManager;
@@ -36,6 +38,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
@@ -96,6 +99,9 @@
}).when(mWifiManager).registerSoftApCallback(any(Executor.class),
any(WifiManager.SoftApCallback.class));
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_show_wifi_tethering, true);
+
Handler handler = new Handler(mLooper.getLooper());
mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
@@ -176,4 +182,18 @@
verify(mCallback1).onHotspotAvailabilityChanged(false);
}
+
+ @Test
+ public void testHotspotSupported_resource_false() {
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_show_wifi_tethering, false);
+
+ Handler handler = new Handler(mLooper.getLooper());
+
+ HotspotController controller =
+ new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+
+ verifyNoMoreInteractions(mTetheringManager);
+ assertFalse(controller.isHotspotSupported());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index 120bf79..b485693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -23,6 +23,8 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -55,6 +57,8 @@
@Mock private lateinit var dismissDialog: () -> Unit
@Mock private lateinit var selectUser: (Int) -> Unit
@Mock private lateinit var switchUser: (Int) -> Unit
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
private lateinit var underTest: GuestUserInteractor
@@ -87,10 +91,18 @@
repository = repository,
),
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
}
@Test
+ fun `registers broadcast receivers`() {
+ verify(resumeSessionReceiver).register()
+ verify(resetOrExitSessionReceiver).register()
+ }
+
+ @Test
fun `onDeviceBootCompleted - allowed to add - create guest`() =
runBlocking(IMMEDIATE) {
setAllowedToAdd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 1680c36c..58f5531 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -21,6 +21,8 @@
import android.app.admin.DevicePolicyManager
import android.os.UserManager
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -48,6 +50,8 @@
@Mock protected lateinit var devicePolicyManager: DevicePolicyManager
@Mock protected lateinit var uiEventLogger: UiEventLogger
@Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
protected lateinit var underTest: UserInteractor
@@ -107,6 +111,8 @@
devicePolicyManager = devicePolicyManager,
refreshUsersScheduler = refreshUsersScheduler,
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c12a868..116023a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,8 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
import com.android.systemui.flags.FakeFeatureFlags
@@ -69,6 +71,8 @@
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
private lateinit var underTest: UserSwitcherViewModel
@@ -104,6 +108,8 @@
devicePolicyManager = devicePolicyManager,
refreshUsersScheduler = refreshUsersScheduler,
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
underTest =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 11178db..627bd09 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import kotlinx.coroutines.flow.Flow
@@ -52,6 +53,12 @@
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+ private val _isBouncerShowing = MutableStateFlow(false)
+ override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+ private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 23c7a61..2d6d29a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -62,7 +62,11 @@
}
@Override
- public void setSignalIcon(String slot, WifiIconState state) {
+ public void setWifiIcon(String slot, WifiIconState state) {
+ }
+
+ @Override
+ public void setNewWifiIcon() {
}
@Override
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 252dcfc..4430bb4b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -254,11 +255,13 @@
private boolean mSafeMode;
private int mMaxWidgetBitmapMemory;
private boolean mIsProviderInfoPersisted;
+ private boolean mIsCombinedBroadcastEnabled;
AppWidgetServiceImpl(Context context) {
mContext = context;
}
+ @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)
public void onStart() {
mPackageManager = AppGlobals.getPackageManager();
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
@@ -277,6 +280,8 @@
mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
&& DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
+ mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
Slog.d(TAG, "App widget provider info will not be persisted on this device");
}
@@ -1123,16 +1128,16 @@
final int widgetCount = provider.widgets.size();
if (widgetCount == 1) {
- // Tell the provider that it's ready.
- sendEnableIntentLocked(provider);
+ // If we are binding the very first widget from a provider, we will send
+ // a combined broadcast or 2 separate broadcasts to tell the provider that
+ // it's ready, and we need them to provide the update now.
+ sendEnableAndUpdateIntentLocked(provider, new int[]{appWidgetId});
+ } else {
+ // For any widget other then the first one, we just send update intent
+ // as we normally would.
+ sendUpdateIntentLocked(provider, new int[]{appWidgetId});
}
- // Send an update now -- We need this update now, and just for this appWidgetId.
- // It's less critical when the next one happens, so when we schedule the next one,
- // we add updatePeriodMillis to its start time. That time will have some slop,
- // but that's okay.
- sendUpdateIntentLocked(provider, new int[] {appWidgetId});
-
// Schedule the future updates.
registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
@@ -2361,6 +2366,22 @@
cancelBroadcastsLocked(provider);
}
+ private void sendEnableAndUpdateIntentLocked(@NonNull Provider p, int[] appWidgetIds) {
+ final boolean canSendCombinedBroadcast = mIsCombinedBroadcastEnabled && p.info != null
+ && p.info.isExtendedFromAppWidgetProvider;
+ if (!canSendCombinedBroadcast) {
+ // If this function is called by mistake, send two separate broadcasts instead
+ sendEnableIntentLocked(p);
+ sendUpdateIntentLocked(p, appWidgetIds);
+ return;
+ }
+
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ intent.setComponent(p.id.componentName);
+ sendBroadcastAsUser(intent, p.id.getProfile());
+ }
+
private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.id.componentName);
@@ -2852,7 +2873,6 @@
if (provider.widgets.size() > 0) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"appwidget init " + provider.id.componentName.getPackageName());
- sendEnableIntentLocked(provider);
provider.widgets.forEach(widget -> {
widget.trackingUpdate = true;
Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -2861,7 +2881,7 @@
Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
});
int[] appWidgetIds = getWidgetIds(provider.widgets);
- sendUpdateIntentLocked(provider, appWidgetIds);
+ sendEnableAndUpdateIntentLocked(provider, appWidgetIds);
registerForBroadcastsLocked(provider, appWidgetIds);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index df5113b..85d6f293 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2886,10 +2886,13 @@
|| event == Event.ACTIVITY_DESTROYED)) {
contentCaptureService.notifyActivityEvent(userId, activity, event);
}
- // TODO(b/201234353): Move the logic to client side.
- if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED
- || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) {
- mVoiceInteractionManagerProvider.notifyActivityEventChanged();
+ // Currently we have move most of logic to the client side. When the activity lifecycle
+ // event changed, the client side will notify the VoiceInteractionManagerService. But
+ // when the application process died, the VoiceInteractionManagerService will miss the
+ // activity lifecycle event changed, so we still need ACTIVITY_DESTROYED event here to
+ // know if the activity has been destroyed.
+ if (mVoiceInteractionManagerProvider != null && event == Event.ACTIVITY_DESTROYED) {
+ mVoiceInteractionManagerProvider.notifyActivityDestroyed(appToken);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index ca4b747..dbe971f3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -100,9 +100,7 @@
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */, lockoutCache,
allowBackgroundAuthentication,
- context.getResources().getBoolean(
- com.android.internal.R.bool.system_server_plays_face_haptics)
- /* shouldVibrate */,
+ false /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 9baca98..91eec7d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -74,7 +74,7 @@
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */,
- lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
+ lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index a778b57..9aecf78 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -69,7 +69,6 @@
class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
- private static final int MESSAGE_IGNORE_AUTH = 1;
private static final int MESSAGE_AUTH_SUCCESS = 2;
private static final int MESSAGE_FINGER_UP = 3;
@NonNull
@@ -136,7 +135,7 @@
taskStackListener,
lockoutCache,
allowBackgroundAuthentication,
- true /* shouldVibrate */,
+ false /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
@@ -235,12 +234,6 @@
() -> {
long delay = 0;
if (authenticated && mSensorProps.isAnySidefpsType()) {
- if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
- Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0,
- true);
- return;
- }
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
if (mSideFpsLastAcquireStartTime != -1) {
@@ -497,16 +490,12 @@
if (mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): onPowerPressed");
mHandler.post(() -> {
- if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
- Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
- mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
- // Do not call onError() as that will send an additional callback to coex.
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
- }
- mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
- mHandler.postDelayed(() -> {
- }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+ Slog.i(TAG, "(sideFPS): finishing auth");
+ // Ignore auths after a power has been detected
+ mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+ // Do not call onError() as that will send an additional callback to coex.
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ mSensorOverlays.hide(getSensorId());
});
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7ed1a51..0d620fd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -81,7 +81,7 @@
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
- true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
+ false /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 5589673..e4aa5e5 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -157,7 +157,7 @@
mDozeConfig = new AmbientDisplayConfiguration(mContext);
mUiEventLogger = new UiEventLoggerImpl();
mDreamUiEventLogger = new DreamUiEventLoggerImpl(
- mContext.getResources().getString(R.string.config_loggable_dream_prefix));
+ mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
mDreamsOnlyEnabledForSystemUser =
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
index 26ca74a..96ebcbb 100644
--- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -26,10 +26,10 @@
* @hide
*/
public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
- final String mLoggableDreamPrefix;
+ private final String[] mLoggableDreamPrefixes;
- DreamUiEventLoggerImpl(String loggableDreamPrefix) {
- mLoggableDreamPrefix = loggableDreamPrefix;
+ DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) {
+ mLoggableDreamPrefixes = loggableDreamPrefixes;
}
@Override
@@ -38,13 +38,20 @@
if (eventID <= 0) {
return;
}
- final boolean isFirstPartyDream =
- mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
- mLoggableDreamPrefix);
FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
/* uid = 1 */ 0,
/* event_id = 2 */ eventID,
/* instance_id = 3 */ 0,
- /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+ /* dream_component_name = 4 */
+ isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other");
+ }
+
+ private boolean isFirstPartyDream(String dreamComponentName) {
+ for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) {
+ if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2eb2cf6..e86f34b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -812,7 +812,6 @@
StartingData mStartingData;
WindowState mStartingWindow;
StartingSurfaceController.StartingSurface mStartingSurface;
- boolean startingDisplayed;
boolean startingMoved;
/** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@
/** Whether the input to this activity will be dropped during the current playing animation. */
private boolean mIsInputDroppedForAnimation;
- /**
- * If it is non-null, it requires all activities who have the same starting data to be drawn
- * to remove the starting window.
- * TODO(b/189385912): Remove starting window related fields after migrating them to task.
- */
- private StartingData mSharedStartingData;
-
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1200,14 +1192,11 @@
pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
pw.print(" mIsExiting="); pw.println(mIsExiting);
}
- if (mSharedStartingData != null) {
- pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
- }
- if (mStartingWindow != null || mStartingSurface != null
- || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+ if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+ || startingMoved || mVisibleSetFromTransferredStartingWindow) {
pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
pw.print(" startingSurface="); pw.print(mStartingSurface);
- pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+ pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
pw.print(" startingMoved="); pw.print(startingMoved);
pw.println(" mVisibleSetFromTransferredStartingWindow="
+ mVisibleSetFromTransferredStartingWindow);
@@ -2690,13 +2679,23 @@
}
}
+ boolean isStartingWindowDisplayed() {
+ final StartingData data = mStartingData != null ? mStartingData : task != null
+ ? task.mSharedStartingData : null;
+ return data != null && data.mIsDisplayed;
+ }
+
/** Called when the starting window is added to this activity. */
void attachStartingWindow(@NonNull WindowState startingWindow) {
startingWindow.mStartingData = mStartingData;
mStartingWindow = startingWindow;
- // The snapshot type may have called associateStartingDataWithTask().
- if (mStartingData != null && mStartingData.mAssociatedTask != null) {
- attachStartingSurfaceToAssociatedTask();
+ if (mStartingData != null) {
+ if (mStartingData.mAssociatedTask != null) {
+ // The snapshot type may have called associateStartingDataWithTask().
+ attachStartingSurfaceToAssociatedTask();
+ } else if (isEmbedded()) {
+ associateStartingWindowWithTaskIfNeeded();
+ }
}
}
@@ -2711,11 +2710,7 @@
/** Called when the starting window is not added yet but its data is known to fill the task. */
private void associateStartingDataWithTask() {
mStartingData.mAssociatedTask = task;
- task.forAllActivities(r -> {
- if (r.mVisibleRequested && !r.firstWindowDrawn) {
- r.mSharedStartingData = mStartingData;
- }
- });
+ task.mSharedStartingData = mStartingData;
}
/** Associates and attaches an added starting window to the current task. */
@@ -2746,10 +2741,8 @@
void removeStartingWindowAnimation(boolean prepareAnimation) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
- if (mSharedStartingData != null) {
- mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
- r.mSharedStartingData = null;
- });
+ if (task != null) {
+ task.mSharedStartingData = null;
}
if (mStartingWindow == null) {
if (mStartingData != null) {
@@ -2772,7 +2765,6 @@
mStartingData = null;
mStartingSurface = null;
mStartingWindow = null;
- startingDisplayed = false;
if (surface == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
+ "startingSurface==null, couldn't remove");
@@ -4257,7 +4249,7 @@
* @return {@code true} if starting window is in app's hierarchy.
*/
boolean hasStartingWindow() {
- if (startingDisplayed || mStartingData != null) {
+ if (mStartingData != null) {
return true;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4355,10 +4347,7 @@
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
mStartingSurface = fromActivity.mStartingSurface;
- startingDisplayed = fromActivity.startingDisplayed;
- fromActivity.startingDisplayed = false;
mStartingWindow = tStartingWindow;
reportedVisible = fromActivity.reportedVisible;
fromActivity.mStartingData = null;
@@ -4424,7 +4413,6 @@
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Moving pending starting from %s to %s", fromActivity, this);
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
fromActivity.mStartingData = null;
fromActivity.startingMoved = true;
scheduleAddStartingWindow();
@@ -6534,14 +6522,11 @@
// Remove starting window directly if is in a pure task. Otherwise if it is associated with
// a task (e.g. nested task fragment), then remove only if all visible windows in the task
// are drawn.
- final Task associatedTask =
- mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+ final Task associatedTask = task.mSharedStartingData != null ? task : null;
if (associatedTask == null) {
removeStartingWindow();
- } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
- // Don't block starting window removal if an Activity can't be a starting window
- // target.
- && r.mSharedStartingData != null) == null) {
+ } else if (associatedTask.getActivity(
+ r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -6756,7 +6741,6 @@
if (mLastTransactionSequence != mWmService.mTransactionSequence) {
mLastTransactionSequence = mWmService.mTransactionSequence;
mNumDrawnWindows = 0;
- startingDisplayed = false;
// There is the main base application window, even if it is exiting, wait for it
mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6800,9 +6784,9 @@
isInterestingAndDrawn = true;
}
}
- } else if (w.isDrawn()) {
+ } else if (mStartingData != null && w.isDrawn()) {
// The starting window for this container is drawn.
- startingDisplayed = true;
+ mStartingData.mIsDisplayed = true;
}
}
@@ -7550,7 +7534,8 @@
ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
+ ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
- this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+ this, reportedVisible, okToDisplay(), okToAnimate(),
+ isStartingWindowDisplayed());
// clean up thumbnail window
if (mThumbnail != null) {
@@ -9649,7 +9634,7 @@
if (mStartingWindow != null) {
mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
}
- proto.write(STARTING_DISPLAYED, startingDisplayed);
+ proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
proto.write(STARTING_MOVED, startingMoved);
proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 9c95e31..671524b 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1229,11 +1229,11 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.startingDisplayed,
+ activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+ if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
return false;
}
if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index e7ab63e..13a1cb6 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -216,14 +216,10 @@
return;
}
- if (container != null) {
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- } else {
- t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
- }
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
t.setAlpha(d.mDimLayer, alpha);
t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
@@ -231,32 +227,6 @@
}
/**
- * Finish a dim started by dimAbove in the case there was no call to dimAbove.
- *
- * @param t A Transaction in which to finish the dim.
- */
- void stopDim(SurfaceControl.Transaction t) {
- if (mDimState != null) {
- t.hide(mDimState.mDimLayer);
- mDimState.isVisible = false;
- mDimState.mDontReset = false;
- }
- }
-
- /**
- * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
- * remove this effect. If the Dim can be assosciated with a particular child of the host
- * consider using the other variant of dimAbove which ties the Dim lifetime to the child
- * lifetime more explicitly.
- *
- * @param t A transaction in which to apply the Dim.
- * @param alpha The alpha at which to Dim.
- */
- void dimAbove(SurfaceControl.Transaction t, float alpha) {
- dim(t, null, 1, alpha, 0);
- }
-
- /**
* Place a dim above the given container, which should be a child of the host container.
* for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
* and the child should call dimAbove again to request the Dim to continue.
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index f11c2a7..dcb7fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -604,7 +604,10 @@
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
}
if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(false /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(false /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
} catch (RemoteException ex) {
@@ -619,7 +622,10 @@
void showLockTaskToast() {
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
try {
- getStatusBarService().showPinningEscapeToast();
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEscapeToast();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to send pinning escape toast", e);
}
@@ -727,7 +733,10 @@
// When lock task starts, we disable the status bars.
try {
if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(true /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(true /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(lockTaskModeState);
mLockTaskModeState = lockTaskModeState;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343..300a894 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@
*/
Task mAssociatedTask;
+ /** Whether the starting window is drawn. */
+ boolean mIsDisplayed;
+
protected StartingData(WindowManagerService service, int typeParams) {
mService = service;
mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index eba49bb..66d7af9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@
int mLockTaskUid = -1; // The uid of the application that called startLockTask().
+ /**
+ * If non-null, the starting window should cover the associated task. It is assigned when the
+ * parent activity of starting window is put in a partial area of the task. This field will be
+ * cleared when all visible activities in this task are drawn.
+ */
+ StartingData mSharedStartingData;
+
/** The process that had previously hosted the root activity of this task.
* Used to know that we should try harder to keep this process around, in case the
* user wants to return to it. */
@@ -3688,6 +3695,9 @@
if (mRootProcess != null) {
pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
}
+ if (mSharedStartingData != null) {
+ pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+ }
pw.print(prefix); pw.print("taskId=" + mTaskId);
pw.println(" rootTaskId=" + getRootTaskId());
pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d178676..879c7e2 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1890,10 +1890,10 @@
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
- // There may be a trampoline activity without window on top of the existing task
- // which is moving to front. Exclude the finishing activity so the window of next
- // activity can be chosen to create the animation target.
- ? getTopNonFinishingActivity()
+ // There may be a launching (e.g. trampoline or embedded) activity without a window
+ // on top of the existing task which is moving to front. Exclude finishing activity
+ // so the window of next activity can be chosen to create the animation target.
+ ? getActivity(r -> !r.finishing && r.hasChild())
: getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ac720be..9c9d751 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8684,11 +8684,12 @@
h.ownerPid = callingPid;
if (region == null) {
- h.replaceTouchableRegionWithCrop = true;
+ h.replaceTouchableRegionWithCrop(null);
} else {
h.touchableRegion.set(region);
+ h.replaceTouchableRegionWithCrop = false;
+ h.setTouchableRegionCrop(surface);
}
- h.setTouchableRegionCrop(null /* use the input surface's bounds */);
final SurfaceControl.Transaction t = mTransactionFactory.get();
t.setInputWindowInfo(surface, h);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 744bf0a..d4c1abf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1842,8 +1842,8 @@
* @return {@code true} if one or more windows have been displayed, else false.
*/
boolean hasAppShownWindows() {
- return mActivityRecord != null
- && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+ return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+ || mActivityRecord.isStartingWindowDisplayed());
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6e16b5d..a0ba8fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- final ActivityRecord activity = mWin.mActivityRecord;
- if (activity != null) {
- if (mWin == activity.mStartingWindow) {
- activity.startingDisplayed = false;
- }
- }
-
if (mSurfaceController == null) {
return;
}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 7610b7c..b33e22f 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -164,7 +164,7 @@
}
public void testRequestPinAppWidget() {
- ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
// Set up users.
when(mMockShortcutService.requestPinAppWidget(anyString(),
any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt()))
@@ -289,6 +289,16 @@
assertEquals(4, updates.size());
}
+ public void testReceiveBroadcastBehavior_enableAndUpdate() {
+ TestAppWidgetProvider testAppWidgetProvider = new TestAppWidgetProvider();
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
+
+ testAppWidgetProvider.onReceive(mTestContext, intent);
+
+ assertTrue(testAppWidgetProvider.isBehaviorSuccess());
+ }
+
+
public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
int widgetId = setupHostAndWidget();
int widgetId2 = bindNewWidget();
@@ -385,7 +395,7 @@
}
private int bindNewWidget() {
- ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java b/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java
deleted file mode 100644
index fd99b21..0000000
--- a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.appwidget;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Placeholder widget for testing
- */
-public class DummyAppWidget extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java
new file mode 100644
index 0000000..6c11a68
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appwidget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+
+/**
+ * Placeholder widget for testing
+ */
+public class TestAppWidgetProvider extends AppWidgetProvider {
+ private boolean mEnabled;
+ private boolean mUpdated;
+
+ TestAppWidgetProvider() {
+ super();
+ mEnabled = false;
+ mUpdated = false;
+ }
+
+ public boolean isBehaviorSuccess() {
+ return mEnabled && mUpdated;
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager,
+ int[] appWidgetids) {
+ mUpdated = true;
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ mEnabled = true;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 606f486..cd4af0a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -616,6 +616,20 @@
verify(mCallback).onClientFinished(any(), eq(true));
}
+ @Test
+ public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ client.onPowerPressed();
+ mLooper.dispatchAll();
+
+ verify(mCallback, never()).onClientFinished(any(), eq(true));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 462957a..8a0a4f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2886,6 +2886,7 @@
fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
task.addChild(taskFragment1, POSITION_TOP);
assertEquals(task, activity1.mStartingData.mAssociatedTask);
+ assertEquals(activity1.mStartingData, task.mSharedStartingData);
final TaskFragment taskFragment2 = new TaskFragment(
mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2905,7 +2906,6 @@
verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
eq(task.mSurfaceControl));
- assertEquals(activity1.mStartingData, startingWindow.mStartingData);
assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
assertEquals(taskFragment1.getBounds(), activity1.getBounds());
// The activity was resized by task fragment, but starting window must still cover the task.
@@ -2916,6 +2916,7 @@
activity1.onFirstWindowDrawn(activityWindow);
activity2.onFirstWindowDrawn(activityWindow);
assertNull(activity1.mStartingWindow);
+ assertNull(task.mSharedStartingData);
}
@Test
@@ -2991,10 +2992,10 @@
final WindowManager.LayoutParams attrs =
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
final TestWindowState startingWindow = createWindowState(attrs, activity);
- activity.startingDisplayed = true;
+ activity.mStartingData = mock(StartingData.class);
activity.addWindow(startingWindow);
assertTrue("Starting window should be present", activity.hasStartingWindow());
- activity.startingDisplayed = false;
+ activity.mStartingData = null;
assertTrue("Starting window should be present", activity.hasStartingWindow());
activity.removeChild(startingWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d..0332c4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@
activity.allDrawn = true;
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
+ // Assume the activity contains a window.
+ doReturn(true).when(activity).hasChild();
// Make sure activity can create remote animation target.
doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f61effa..d55e53c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -321,7 +321,6 @@
final ActivityRecord activity2 = createActivityRecord(dc2);
activity1.allDrawn = true;
- activity1.startingDisplayed = true;
activity1.startingMoved = true;
// Simulate activity resume / finish flows to prepare app transition & set visibility,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 55a7c1b..befe4e8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -24,7 +24,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -139,34 +138,12 @@
}
@Test
- public void testDimAboveNoChildCreatesSurface() {
- final float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- SurfaceControl dimLayer = getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mTransaction).setAlpha(dimLayer, alpha);
- verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
- }
-
- @Test
- public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() {
- float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
- final SurfaceControl firstSurface = getDimLayer();
-
- alpha = 0.9f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- assertEquals(firstSurface, getDimLayer());
- verify(mTransaction).setAlpha(firstSurface, 0.9f);
- }
-
- @Test
public void testUpdateDimsAppliesCrop() {
- mDimmer.dimAbove(mTransaction, 0.8f);
+ TestWindowContainer child = new TestWindowContainer(mWm);
+ mHost.addChild(child, 0);
+
+ final float alpha = 0.8f;
+ mDimmer.dimAbove(mTransaction, child, alpha);
int width = 100;
int height = 300;
@@ -178,17 +155,6 @@
}
@Test
- public void testDimAboveNoChildNotReset() {
- mDimmer.dimAbove(mTransaction, 0.8f);
- SurfaceControl dimLayer = getDimLayer();
- mDimmer.resetDimStates();
-
- mDimmer.updateDims(mTransaction, new Rect());
- verify(mTransaction).show(getDimLayer());
- verify(mTransaction, never()).remove(dimLayer);
- }
-
- @Test
public void testDimAboveWithChildCreatesSurfaceAboveChild() {
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 352d8d1..bc5c9ec 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -172,11 +172,11 @@
mAmInternal.setVoiceInteractionManagerProvider(
new ActivityManagerInternal.VoiceInteractionManagerProvider() {
@Override
- public void notifyActivityEventChanged() {
+ public void notifyActivityDestroyed(IBinder activityToken) {
if (DEBUG) {
- Slog.d(TAG, "call notifyActivityEventChanged");
+ Slog.d(TAG, "notifyActivityDestroyed activityToken=" + activityToken);
}
- mServiceStub.notifyActivityEventChanged();
+ mServiceStub.notifyActivityDestroyed(activityToken);
}
});
}
@@ -447,11 +447,12 @@
return mImpl.supportsLocalVoiceInteraction();
}
- void notifyActivityEventChanged() {
+ void notifyActivityDestroyed(@NonNull IBinder activityToken) {
synchronized (this) {
- if (mImpl == null) return;
+ if (mImpl == null || activityToken == null) return;
- Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked());
+ Binder.withCleanCallingIdentity(
+ () -> mImpl.notifyActivityDestroyedLocked(activityToken));
}
}
@@ -1223,6 +1224,16 @@
}
}
+ @Override
+ public void notifyActivityEventChanged(@NonNull IBinder activityToken, int type) {
+ synchronized (this) {
+ if (mImpl == null || activityToken == null) {
+ return;
+ }
+ Binder.withCleanCallingIdentity(
+ () -> mImpl.notifyActivityEventChangedLocked(activityToken, type));
+ }
+ }
//----------------- Hotword Detection/Validation APIs --------------------------------//
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b9793ca..fabab25 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -520,9 +520,23 @@
mActiveSession.stopListeningVisibleActivityChangedLocked();
}
- public void notifyActivityEventChangedLocked() {
+ public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
if (DEBUG) {
- Slog.d(TAG, "notifyActivityEventChangedLocked");
+ Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+ }
+ if (mActiveSession == null || !mActiveSession.mShown) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityDestroyedLocked not allowed on no session or"
+ + " hidden session");
+ }
+ return;
+ }
+ mActiveSession.notifyActivityDestroyedLocked(activityToken);
+ }
+
+ public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
}
if (mActiveSession == null || !mActiveSession.mShown) {
if (DEBUG) {
@@ -531,7 +545,7 @@
}
return;
}
- mActiveSession.notifyActivityEventChangedLocked();
+ mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
}
public void updateStateLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index ae9be8c..b24337f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -29,6 +29,7 @@
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
@@ -59,6 +60,7 @@
import android.service.voice.VisibleActivityInfo;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionSession;
+import android.util.ArrayMap;
import android.util.Slog;
import android.view.IWindowManager;
@@ -128,7 +130,11 @@
private boolean mListeningVisibleActivity;
private final ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor();
- private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
+ // Records the visible activity information the system has already called onVisible, without
+ // confirming the result of callback. When activity visible state is changed, we use this to
+ // determine to call onVisible or onInvisible to assistant application.
+ private final ArrayMap<IBinder, VisibleActivityInfo> mVisibleActivityInfoForToken =
+ new ArrayMap<>();
private final PowerManagerInternal mPowerManagerInternal;
private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
@@ -530,7 +536,7 @@
public void cancelLocked(boolean finishTask) {
mListeningVisibleActivity = false;
- mVisibleActivityInfos.clear();
+ mVisibleActivityInfoForToken.clear();
hideLocked();
mCanceled = true;
if (mBound) {
@@ -608,17 +614,24 @@
if (DEBUG) {
Slog.d(TAG, "startListeningVisibleActivityChangedLocked");
}
- mListeningVisibleActivity = true;
- mVisibleActivityInfos.clear();
- mScheduledExecutorService.execute(() -> {
- if (DEBUG) {
- Slog.d(TAG, "call handleVisibleActivitiesLocked from enable listening");
- }
- synchronized (mLock) {
- handleVisibleActivitiesLocked();
- }
- });
+ if (!mShown || mCanceled || mSession == null) {
+ return;
+ }
+
+ mListeningVisibleActivity = true;
+ mVisibleActivityInfoForToken.clear();
+
+ // It should only need to report which activities are visible
+ final ArrayMap<IBinder, VisibleActivityInfo> newVisibleActivityInfos =
+ getTopVisibleActivityInfosLocked();
+
+ if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
+ return;
+ }
+ notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
+ VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+ mVisibleActivityInfoForToken.putAll(newVisibleActivityInfos);
}
void stopListeningVisibleActivityChangedLocked() {
@@ -626,12 +639,13 @@
Slog.d(TAG, "stopListeningVisibleActivityChangedLocked");
}
mListeningVisibleActivity = false;
- mVisibleActivityInfos.clear();
+ mVisibleActivityInfoForToken.clear();
}
- void notifyActivityEventChangedLocked() {
+ void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
- Slog.d(TAG, "notifyActivityEventChangedLocked");
+ Slog.d(TAG, "notifyActivityEventChangedLocked activityToken=" + activityToken
+ + ", type=" + type);
}
if (!mListeningVisibleActivity) {
if (DEBUG) {
@@ -640,99 +654,139 @@
return;
}
mScheduledExecutorService.execute(() -> {
- if (DEBUG) {
- Slog.d(TAG, "call handleVisibleActivitiesLocked from activity event");
- }
synchronized (mLock) {
- handleVisibleActivitiesLocked();
+ handleVisibleActivitiesLocked(activityToken, type);
}
});
}
- private List<VisibleActivityInfo> getVisibleActivityInfosLocked() {
+ private ArrayMap<IBinder, VisibleActivityInfo> getTopVisibleActivityInfosLocked() {
if (DEBUG) {
- Slog.d(TAG, "getVisibleActivityInfosLocked");
+ Slog.d(TAG, "getTopVisibleActivityInfosLocked");
}
List<ActivityAssistInfo> allVisibleActivities =
LocalServices.getService(ActivityTaskManagerInternal.class)
.getTopVisibleActivities();
if (DEBUG) {
- Slog.d(TAG,
- "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities);
+ Slog.d(TAG, "getTopVisibleActivityInfosLocked: allVisibleActivities="
+ + allVisibleActivities);
}
- if (allVisibleActivities == null || allVisibleActivities.isEmpty()) {
+ if (allVisibleActivities.isEmpty()) {
Slog.w(TAG, "no visible activity");
return null;
}
final int count = allVisibleActivities.size();
- final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count);
+ final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfoArrayMap =
+ new ArrayMap<>(count);
for (int i = 0; i < count; i++) {
ActivityAssistInfo info = allVisibleActivities.get(i);
if (DEBUG) {
- Slog.d(TAG, " : activityToken=" + info.getActivityToken()
+ Slog.d(TAG, "ActivityAssistInfo : activityToken=" + info.getActivityToken()
+ ", assistToken=" + info.getAssistToken()
+ ", taskId=" + info.getTaskId());
}
- visibleActivityInfos.add(
+ visibleActivityInfoArrayMap.put(info.getActivityToken(),
new VisibleActivityInfo(info.getTaskId(), info.getAssistToken()));
}
- return visibleActivityInfos;
+ return visibleActivityInfoArrayMap;
}
- private void handleVisibleActivitiesLocked() {
+ // TODO(b/242359988): Split this method up
+ private void handleVisibleActivitiesLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
- Slog.d(TAG, "handleVisibleActivitiesLocked");
+ Slog.d(TAG, "handleVisibleActivitiesLocked activityToken=" + activityToken
+ + ", type=" + type);
}
- if (mSession == null) {
- return;
- }
- if (!mShown || !mListeningVisibleActivity || mCanceled) {
- return;
- }
- final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked();
- if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(mVisibleActivityInfos,
- VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
- mVisibleActivityInfos.clear();
+ if (!mListeningVisibleActivity) {
+ if (DEBUG) {
+ Slog.d(TAG, "not enable listening visible activity");
+ }
return;
}
- if (mVisibleActivityInfos.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
- VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
- mVisibleActivityInfos.addAll(newVisibleActivityInfos);
+ if (!mShown || mCanceled || mSession == null) {
return;
}
- final List<VisibleActivityInfo> addedActivities = new ArrayList<>();
- final List<VisibleActivityInfo> removedActivities = new ArrayList<>();
+ // We use this local variable to determine to call onVisible or onInvisible.
+ boolean notifyOnVisible = false;
+ VisibleActivityInfo notifyVisibleActivityInfo = null;
- removedActivities.addAll(mVisibleActivityInfos);
- for (int i = 0; i < newVisibleActivityInfos.size(); i++) {
- final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i);
- if (!removedActivities.isEmpty() && removedActivities.contains(
- candidateVisibleActivityInfo)) {
- removedActivities.remove(candidateVisibleActivityInfo);
- } else {
- addedActivities.add(candidateVisibleActivityInfo);
+ if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START
+ || type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME) {
+ // It seems that the onStart is unnecessary. But if we have it, the assistant
+ // application can request the directActions early. Even if we have the onStart,
+ // we still need the onResume because it is possible that the activity goes to
+ // onResume from onPause with invisible before the activity goes to onStop from
+ // onPause.
+
+ // Check if we have reported this activity as visible. If we have reported it as
+ // visible, do nothing.
+ if (mVisibleActivityInfoForToken.containsKey(activityToken)) {
+ return;
+ }
+
+ // Before reporting this activity as visible, we need to make sure the activity
+ // is really visible.
+ notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+ activityToken);
+ if (notifyVisibleActivityInfo == null) {
+ return;
+ }
+ notifyOnVisible = true;
+ } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE) {
+ // For the onPause stage, the Activity is not necessarily invisible now, so we need
+ // to check its state.
+ // Note: After syncing with Activity owner, before the onPause is called, the
+ // visibility state has been updated.
+ notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+ activityToken);
+ if (notifyVisibleActivityInfo != null) {
+ return;
+ }
+
+ // Also make sure we previously reported this Activity as visible.
+ notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+ if (notifyVisibleActivityInfo == null) {
+ return;
+ }
+ } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP) {
+ // For the onStop stage, the activity is in invisible state. We only need to consider if
+ // we have reported this activity as visible. If we have reported it as visible, we
+ // need to report it as invisible.
+ // Why we still need onStop? Because it is possible that the activity is in a visible
+ // state during onPause stage, when the activity enters onStop from onPause, we may
+ // need to notify onInvisible.
+ // Note: After syncing with Activity owner, before the onStop is called, the
+ // visibility state has been updated.
+ notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+ if (notifyVisibleActivityInfo == null) {
+ return;
+ }
+ } else {
+ Slog.w(TAG, "notifyActivityEventChangedLocked unexpected type=" + type);
+ return;
+ }
+
+ try {
+ mSession.notifyVisibleActivityInfoChanged(notifyVisibleActivityInfo,
+ notifyOnVisible ? VisibleActivityInfo.TYPE_ACTIVITY_ADDED
+ : VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "handleVisibleActivitiesLocked RemoteException : " + e);
}
}
- if (!addedActivities.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(addedActivities,
- VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+ if (notifyOnVisible) {
+ mVisibleActivityInfoForToken.put(activityToken, notifyVisibleActivityInfo);
+ } else {
+ mVisibleActivityInfoForToken.remove(activityToken);
}
- if (!removedActivities.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(removedActivities,
- VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
- }
-
- mVisibleActivityInfos.clear();
- mVisibleActivityInfos.addAll(newVisibleActivityInfos);
}
private void notifyVisibleActivitiesChangedLocked(
- List<VisibleActivityInfo> visibleActivityInfos, int type) {
+ ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos, int type) {
if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) {
return;
}
@@ -741,7 +795,7 @@
}
try {
for (int i = 0; i < visibleActivityInfos.size(); i++) {
- mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.get(i), type);
+ mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.valueAt(i), type);
}
} catch (RemoteException e) {
if (DEBUG) {
@@ -754,6 +808,51 @@
}
}
+ private VisibleActivityInfo getVisibleActivityInfoFromTopVisibleActivity(
+ @NonNull IBinder activityToken) {
+ final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos =
+ getTopVisibleActivityInfosLocked();
+ if (visibleActivityInfos == null) {
+ return null;
+ }
+ return visibleActivityInfos.get(activityToken);
+ }
+
+ void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+ }
+ if (!mListeningVisibleActivity) {
+ if (DEBUG) {
+ Slog.d(TAG, "not enable listening visible activity");
+ }
+ return;
+ }
+ mScheduledExecutorService.execute(() -> {
+ synchronized (mLock) {
+ if (!mListeningVisibleActivity) {
+ return;
+ }
+ if (!mShown || mCanceled || mSession == null) {
+ return;
+ }
+
+ VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfoForToken.remove(
+ activityToken);
+ if (visibleActivityInfo != null) {
+ try {
+ mSession.notifyVisibleActivityInfoChanged(visibleActivityInfo,
+ VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "notifyVisibleActivityInfoChanged RemoteException : " + e);
+ }
+ }
+ }
+ }
+ });
+ }
+
private void removeFromLowPowerStandbyAllowlist() {
synchronized (mLock) {
if (mLowPowerStandbyAllowlisted) {