diff options
136 files changed, 3037 insertions, 1576 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 855366abae4e..81c3e8957cd1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -87,10 +87,12 @@ import android.os.Parcelable; 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 android.window.WindowOnBackInvokedDispatcher; 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 @@ public class Activity extends ContextThemeWrapper 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 @@ public class Activity extends ContextThemeWrapper mCalled = true; notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START); + + notifyVoiceInteractionManagerServiceActivityEvent( + VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START); } /** @@ -2019,6 +2044,12 @@ public class Activity extends ContextThemeWrapper 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 @@ public class Activity extends ContextThemeWrapper getAutofillClientController().onActivityPaused(); notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE); + + notifyVoiceInteractionManagerServiceActivityEvent( + VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE); + mCalled = true; } @@ -2623,6 +2658,9 @@ public class Activity extends ContextThemeWrapper 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 f2ea060b1694..c95a7deba61f 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -731,10 +731,9 @@ public abstract class ActivityManagerInternal { */ 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 a432b8dec2cb..fd9496947628 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -42,12 +42,15 @@ import android.os.Handler; 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 @@ import java.util.List; @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 class AppWidgetManager { 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 class AppWidgetManager { 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 @@ public class AppWidgetManager { 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 a5d2198a6e17..3344ebc083a2 100644 --- a/core/java/android/appwidget/AppWidgetProvider.java +++ b/core/java/android/appwidget/AppWidgetProvider.java @@ -58,7 +58,12 @@ public class AppWidgetProvider extends BroadcastReceiver { // 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 0fd164de8ffb..085bfca158c1 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -918,7 +918,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - /** * 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 c9fd1295a6e1..a955dbba97e7 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9793,6 +9793,13 @@ public final class Settings { "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 df727e9b7d3d..48f732ec9f12 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -19,6 +19,7 @@ package android.service.voice; 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 com.android.internal.util.function.pooled.PooledLambda; 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 class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall */ 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 681693b1dbad..bd51f12539e8 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -18,6 +18,8 @@ package com.android.internal.app; 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.ParcelFileDescriptor; 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 @@ interface IVoiceInteractionManagerService { * 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 6c689ff2b725..44997b4a9c30 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 final class SystemUiDeviceConfigFlags { */ 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 f7467b5a9f86..7787b7c61593 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 988303ea7bc5..4b27bf2849fb 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 aa9a9499ed97..bd8a42920af6 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 7a7b43a3632a..44d84683abf9 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 1d513e444050..16760e26b3f1 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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 79603233ae14..362f1fa096cc 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 000000000000..7237d2bde39f --- /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/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 5b7d141591ea..295a2e3c4244 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -86,9 +86,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private static final int FLING_ENTER_DURATION = 350; private static final int FLING_EXIT_DURATION = 350; - private final int mDividerWindowWidth; - private final int mDividerInsets; - private final int mDividerSize; + private int mDividerWindowWidth; + private int mDividerInsets; + private int mDividerSize; private final Rect mTempRect = new Rect(); private final Rect mRootBounds = new Rect(); @@ -131,6 +131,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mContext = context.createConfigurationContext(configuration); mOrientation = configuration.orientation; mRotation = configuration.windowConfiguration.getRotation(); + mDensity = configuration.densityDpi; mSplitLayoutHandler = splitLayoutHandler; mDisplayImeController = displayImeController; mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration, @@ -139,24 +140,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId()); mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType); - final Resources resources = context.getResources(); - mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width); - mDividerInsets = getDividerInsets(resources, context.getDisplay()); - mDividerWindowWidth = mDividerSize + 2 * mDividerInsets; + updateDividerConfig(mContext); mRootBounds.set(configuration.windowConfiguration.getBounds()); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); resetDividerPosition(); - mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide); + mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide); updateInvisibleRect(); } - private int getDividerInsets(Resources resources, Display display) { + private void updateDividerConfig(Context context) { + final Resources resources = context.getResources(); + final Display display = context.getDisplay(); final int dividerInset = resources.getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_insets); - int radius = 0; RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT); radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; @@ -167,7 +166,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; - return Math.max(dividerInset, radius); + mDividerInsets = Math.max(dividerInset, radius); + mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width); + mDividerWindowWidth = mDividerSize + 2 * mDividerInsets; } /** Gets bounds of the primary split with screen based coordinate. */ @@ -309,6 +310,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRotation = rotation; mDensity = density; mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); + updateDividerConfig(mContext); initDividerPosition(mTempRect); updateInvisibleRect(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index 16f1d1c2944c..f81c9f80830a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.RemoteAction; @@ -70,6 +71,11 @@ public interface PipMenuController { void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction); /** + * Wait until the next frame to run the given Runnable. + */ + void runWithNextFrame(@NonNull Runnable runnable); + + /** * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a * need to synchronize the movements on the same frame as PiP. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f170e774739f..2d7c5ce6feb5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -179,8 +179,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // This is necessary in case there was a resize animation ongoing when exit PIP // started, in which case the first resize will be skipped to let the exit // operation handle the final resize out of PIP mode. See b/185306679. - finishResize(tx, destinationBounds, direction, animationType); - sendOnPipTransitionFinished(direction); + finishResizeDelayedIfNeeded(() -> { + finishResize(tx, destinationBounds, direction, animationType); + sendOnPipTransitionFinished(direction); + }); } } @@ -196,6 +198,34 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; + /** + * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu. + * + * This is done to avoid a race condition between the last transaction applied in + * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a + * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it + * gets applied before the transaction created by the last onAnimationUpdate. As a result of + * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which + * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize + * operation until the next frame. This aligns the last onAnimationUpdate transaction with the + * WCT application. + * + * The race only happens when the PiP surface transaction has to be synced with the PiP menu + * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and + * the PiP menu contents. + */ + private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) { + if (!shouldSyncPipTransactionWithMenu()) { + finishResizeRunnable.run(); + return; + } + mPipMenuController.runWithNextFrame(finishResizeRunnable); + } + + private boolean shouldSyncPipTransactionWithMenu() { + return mPipMenuController.isMenuVisible(); + } + @VisibleForTesting final PipTransitionController.PipTransitionCallback mPipTransitionCallback = new PipTransitionController.PipTransitionCallback() { @@ -221,7 +251,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @Override public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds) { - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(leash, tx, destinationBounds); return true; } @@ -1223,7 +1253,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .crop(tx, mLeash, toBounds) .round(tx, mLeash, mPipTransitionState.isInPip()); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.resizePipMenu(mLeash, tx, toBounds); } else { tx.apply(); @@ -1265,7 +1295,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .scale(tx, mLeash, startBounds, toBounds, degrees) .round(tx, mLeash, startBounds, toBounds); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(mLeash, tx, toBounds); } else { tx.apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 281ea530e9e1..27902b2231ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -305,6 +305,18 @@ public class PhonePipMenuController implements PipMenuController { showResizeHandle); } + @Override + public void runWithNextFrame(Runnable runnable) { + if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { + runnable.run(); + } + + mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> { + mMainHandler.post(runnable); + }); + mPipMenuView.invalidate(); + } + /** * Move the PiP menu, which does a translation and possibly a scale transformation. */ 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 616d447247de..d28a9f3cf8ff 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 975d4bba276e..a9a97beb9180 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 @@ public class PipTouchHandler { // 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/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 4ce45e142c64..7d4b43be4f73 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -466,6 +466,18 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } @Override + public void runWithNextFrame(Runnable runnable) { + if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { + runnable.run(); + } + + mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> { + mMainHandler.post(runnable); + }); + mPipMenuView.invalidate(); + } + + @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction, Rect pipDestBounds) { if (DEBUG) { diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index f4efc374ecc2..1c28c3d58ccb 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -6,3 +6,4 @@ pablogamito@google.com 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 453a71327b50..8946516af8a2 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 @@ public class SecureSettings { 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 a39735ffe2c7..cbf79530a6b6 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 @@ public class SecureSettingsValidators { 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/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index e743ec87bd1c..bfbe88c475ac 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.Rect; 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 @@ oneway interface IOverviewProxy { 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 @@ oneway interface IOverviewProxy { 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 @@ oneway interface IOverviewProxy { * 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 2b2b05ce2fbf..b99b72bb4275 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 @@ interface ISystemUiProxy { /** 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 d48d7ffcd824..c9b8712bdde9 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -228,7 +228,7 @@ open class ClockEventController @Inject constructor( listenForDozing(this) if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { listenForDozeAmountTransition(this) - listenForGoneToAodTransition(this) + listenForAnyStateToAodTransition(this) } else { listenForDozeAmount(this) } @@ -286,10 +286,10 @@ open class ClockEventController @Inject constructor( * 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 71470e8870de..a0206f1f1e70 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -35,6 +35,7 @@ data class KeyguardFingerprintListenModel( 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 c756a17976bf..2bb3a5f437f5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -106,8 +106,9 @@ public class KeyguardSecurityContainer extends ConstraintLayout { 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 @@ public class KeyguardSecurityContainer extends ConstraintLayout { 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 @@ public class KeyguardSecurityContainer extends ConstraintLayout { 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 79a01b9c9717..fbb114c72add 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -318,6 +318,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard @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 bad75e8dd862..558869c46373 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.StatusBarState; 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected Handler getHandler() { return mHandler; } + private final Handler mHandler; private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = @@ -720,6 +725,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Context context, @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, + SecureSettings secureSettings, DumpManager dumpManager, @Background Executor backgroundExecutor, @Main Executor mainExecutor, @@ -1988,6 +1996,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStatusBarState = mStatusBarStateController.getState(); mLockPatternUtils = lockPatternUtils; mAuthController = authController; + mSecureSettings = secureSettings; dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = sensorPrivacyManager; mActiveUnlockConfig = activeUnlockConfiguration; @@ -2229,9 +2238,35 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * @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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted); final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user); + final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer - && !isEncryptedOrLockdownForUser - && userDoesNotHaveTrust); + && !isEncryptedOrLockdownForUser + && userDoesNotHaveTrust); + + boolean shouldListenSideFpsState = true; + if (isSfpsSupported() && isSfpsEnrolled()) { + shouldListenSideFpsState = + mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true; + } - final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState - && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut(); + boolean shouldListen = shouldListenKeyguardState && shouldListenUserState + && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut() + && shouldListenSideFpsState; maybeLogListenerModelData( new KeyguardFingerprintListenModel( @@ -2626,6 +2685,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mKeyguardOccluded, mOccludingAppRequestingFp, mIsPrimaryUser, + shouldListenSideFpsState, shouldListenForFingerprintAssistant, mSwitchingUser, isUdfps, @@ -3727,6 +3787,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver); } + if (mSfpsRequireScreenOnToAuthPrefObserver != null) { + mContext.getContentResolver().unregisterContentObserver( + mSfpsRequireScreenOnToAuthPrefObserver); + } + try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { @@ -3799,6 +3864,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 80c6c48cb7ee..0a2d8ec97ba6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -120,7 +120,7 @@ public class AuthContainerView extends LinearLayout @VisibleForTesting final BiometricCallback mBiometricCallback; @Nullable private AuthBiometricView mBiometricView; - @Nullable private AuthCredentialView mCredentialView; + @VisibleForTesting @Nullable AuthCredentialView mCredentialView; private final AuthPanelController mPanelController; private final FrameLayout mFrameLayout; private final ImageView mBackgroundView; @@ -762,6 +762,12 @@ public class AuthContainerView extends LinearLayout } 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 c015a21c7db4..ff18eeea45a5 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.doze.DozeReceiver; 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } } } + + 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mWindowManager = windowManager; mInteractionJankMonitor = jankMonitor; mUdfpsEnrolledForUser = new SparseBooleanArray(); + mSfpsEnrolledForUser = new SparseBooleanArray(); + mVibratorHelper = vibrator; mOrientationListener = new BiometricDisplayListener( context, @@ -866,6 +894,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java index f9e44a0c1724..85cb39849f1f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.Context; import android.util.AttributeSet; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; @@ -34,7 +35,7 @@ import java.util.List; */ public class AuthCredentialPatternView extends AuthCredentialView { - private LockPatternView mLockPatternView; + @VisibleForTesting LockPatternView mLockPatternView; private class UnlockPatternListener implements LockPatternView.OnPatternListener { @@ -93,9 +94,7 @@ public class AuthCredentialPatternView extends AuthCredentialView { @Override protected void onErrorTimeoutFinish() { super.onErrorTimeoutFinish(); - // select to enable marquee unless a screen reader is enabled - mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled() - || !mAccessibilityManager.isTouchExplorationEnabled()); + mLockPatternView.setEnabled(true); } public AuthCredentialPatternView(Context context, AttributeSet attrs) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index 5958e6a436f1..157f14fb4c51 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -47,6 +47,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.StringRes; +import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; @@ -98,7 +99,7 @@ public abstract class AuthCredentialView extends LinearLayout { protected int mUserId; protected long mOperationId; protected int mEffectiveUserId; - protected ErrorTimer mErrorTimer; + @VisibleForTesting ErrorTimer mErrorTimer; protected @Background DelayableExecutor mBackgroundExecutor; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 96fe65f8fd40..65fcd760360c 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.SysUISingleton; 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 @@ public class UdfpsController implements DozeReceiver { @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 @@ public class UdfpsController implements DozeReceiver { @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 @@ public class UdfpsController implements DozeReceiver { mUnlockedScreenOffAnimationController, mUdfpsDisplayMode, requestId, reason, callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, - fromUdfpsView), mActivityLaunchAnimator))); + fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags, + mBouncerInteractor))); } @Override @@ -590,6 +595,7 @@ public class UdfpsController implements DozeReceiver { @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 @@ public class UdfpsController implements DozeReceiver { @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 @@ public class UdfpsController implements DozeReceiver { 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 7d0109686351..d70861ac5f19 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.keyguard.KeyguardUpdateMonitor 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 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" */ @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 @@ class UdfpsControllerOverlay @JvmOverloads constructor( 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 4d7f89d7b727..000000000000 --- 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 000000000000..5bae2dc502d6 --- /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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c5d598236c20..28378c93501b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -335,7 +335,7 @@ object Flags { 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 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 99ae85d7a548..80c6130955c5 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 @@ package com.android.systemui.keyguard.data 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 @@ interface BouncerViewDelegate { 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/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index 543389e0a7cd..0046256c677b 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.KeyguardUpdateMonitor 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 @@ constructor( /** 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 @@ constructor( 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 @@ constructor( _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 @@ constructor( _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/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 6baaf5f10024..c867c6e9229b 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.dagger.SysUISingleton 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 @@ interface KeyguardRepository { */ 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 @@ interface KeyguardRepository { /** 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 @@ constructor( private val keyguardStateController: KeyguardStateController, dozeHost: DozeHost, wakefulnessLifecycle: WakefulnessLifecycle, + biometricUnlockController: BiometricUnlockController, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -159,6 +169,29 @@ constructor( 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 @@ constructor( 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 @@ constructor( } } + 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 000000000000..7e01db34319f --- /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 2af9318d92ec..dbb0352c2187 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.dagger.qualifiers.Main 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.phone.KeyguardBypassController 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 @@ constructor( KeyguardBouncerModel( promptReason = repository.bouncerPromptReason ?: 0, errorMessage = repository.bouncerErrorMessage, - expansionAmount = repository.expansionAmount.value + expansionAmount = repository.panelExpansionAmount.value ) ) repository.setShowingSoon(false) @@ -90,14 +90,22 @@ constructor( 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 @@ constructor( Trace.beginSection("KeyguardBouncer#show") repository.setScrimmed(isScrimmed) if (isScrimmed) { - setExpansion(KeyguardBouncer.EXPANSION_VISIBLE) + setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE) } if (resumeBouncer) { @@ -149,7 +157,6 @@ constructor( } keyguardStateController.notifyBouncerShowing(true) callbackInteractor.dispatchStartingToShow() - Trace.endSection() } @@ -168,7 +175,6 @@ constructor( keyguardStateController.notifyBouncerShowing(false /* showing */) cancelShowRunnable() repository.setShowingSoon(false) - repository.setOnDismissAction(null) repository.setVisible(false) repository.setHide(true) repository.setShow(null) @@ -176,14 +182,17 @@ constructor( } /** - * 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 @@ constructor( 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 @@ constructor( /** 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 @@ constructor( /** 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 @@ constructor( /** 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 03c6a789326e..614ff8d930d8 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 @@ package com.android.systemui.keyguard.domain.interactor 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 @@ constructor( 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/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 83d94325b9d4..57fb4a114700 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 @@ constructor( } 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 d5ea77b8e729..a7c6d4450336 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 @@ constructor( 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 dffd097a77c5..749183e241e5 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.dagger.SysUISingleton 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 @@ constructor( /** 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 @@ constructor( 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 761f3fd9d9f9..fd4814d2bc94 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 @@ class LockscreenBouncerTransitionInteractor @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 @@ constructor( 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 @@ constructor( } } } + + 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 728bafae2a4c..37f33afbf53e 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 @@ abstract class StartKeyguardTransitionModule { @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 000000000000..db709b476c5d --- /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/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 0ca358210813..732a6f7b887a 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 @@ data class TransitionStep( 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 df260148751c..a22958b74bb9 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.keyguard.dagger.KeyguardBouncerComponent 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 @@ object KeyguardBouncerViewBinder { 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 @@ object KeyguardBouncerViewBinder { } 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 9ad52117bfc6..9a9284371074 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 android.view.View 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 @@ constructor( 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 @@ constructor( /** 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 c089511a7ce9..85d15dca12cb 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.InsetsVisibilities; 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 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } @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 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; + 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 @@ public class NavigationBar extends ViewController<NavigationBarView> implements 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 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.getViewTreeObserver().addOnComputeInternalInsetsListener( mOnComputeInternalInsetsListener); + mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback); + notifyNavigationBarSurface(); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); @@ -779,6 +795,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements 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 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } } + 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 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } 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 622f5a279a5f..83c2a5de5c6e 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 @@ public class KeyButtonView extends ImageView implements ButtonInterface { 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 709467ffd3b5..c319a827da44 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 @@ public class EdgeBackGestureHandler extends CurrentUserTracker 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 @@ public class EdgeBackGestureHandler extends CurrentUserTracker 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 @@ public class EdgeBackGestureHandler extends CurrentUserTracker 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 e9a6c25c0e6d..1f92b12c1a83 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 @@ public class QSIconViewImpl extends QSIconView { 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 66be00d8de66..46c4f410d078 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.KeyCharacterMap; 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 @@ public class OverviewProxyService extends CurrentUserTracker implements private final UiEventLogger mUiEventLogger; private Region mActiveNavBarRegion; + private SurfaceControl mNavigationBarSurface; private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; @@ -190,7 +192,8 @@ public class OverviewProxyService extends CurrentUserTracker implements // 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 @@ public class OverviewProxyService extends CurrentUserTracker implements } @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 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void notifySwipeUpGestureStarted() { - verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () -> - notifySwipeUpGestureStartedInternal()); - } - - @Override public void notifyPrioritizedRotation(@Surface.Rotation int rotation) { verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () -> notifyPrioritizedRotationInternal(rotation)); @@ -443,6 +438,7 @@ public class OverviewProxyService extends CurrentUserTracker implements Log.e(TAG_OPS, "Failed to call onInitialize()", e); } dispatchNavButtonBounds(); + dispatchNavigationBarSurface(); // Force-update the systemui state flags updateSystemUiStateFlags(); @@ -597,11 +593,18 @@ public class OverviewProxyService extends CurrentUserTracker implements .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 @@ public class OverviewProxyService extends CurrentUserTracker implements 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 class OverviewProxyService extends CurrentUserTracker implements } } - 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 @@ public class OverviewProxyService extends CurrentUserTracker implements } } - 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 @@ public class OverviewProxyService extends CurrentUserTracker implements 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 e00f5ebad571..92f5c851f208 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 @@ public final class NotificationPanelViewController { 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 @@ public final class NotificationPanelViewController { SysUiState sysUiState, Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider, KeyguardUnlockAnimationController keyguardUnlockAnimationController, + KeyguardIndicationController keyguardIndicationController, NotificationListContainer notificationListContainer, NotificationStackSizeCalculator notificationStackSizeCalculator, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @@ -779,6 +780,7 @@ public final class NotificationPanelViewController { mResources = mView.getResources(); mKeyguardStateController = keyguardStateController; + mKeyguardIndicationController = keyguardIndicationController; mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; mNotificationShadeWindowController = notificationShadeWindowController; FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get(); @@ -1020,7 +1022,7 @@ public final class NotificationPanelViewController { 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 @@ public final class NotificationPanelViewController { 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 @@ public final class NotificationPanelViewController { 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 final class NotificationPanelViewController { public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; + mHeadsUpManager.addListener(mOnHeadsUpChangedListener); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScrollLayoutController.getHeadsUpCallback(), NotificationPanelViewController.this); @@ -4361,10 +4364,6 @@ public final class NotificationPanelViewController { mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener); } - public ShadeHeadsUpChangedListener getOnHeadsUpChangedListener() { - return mOnHeadsUpChangedListener; - } - public void setHeaderDebugInfo(String text) { if (DEBUG_DRAWABLE) mHeaderDebugInfo = text; } @@ -4644,7 +4643,7 @@ public final class NotificationPanelViewController { mUpdateFlingVelocity = vel; } } else if (!mCentralSurfaces.isBouncerShowing() - && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating() + && !mStatusBarKeyguardViewManager.isShowingAlternateAuth() && !mKeyguardStateController.isKeyguardGoingAway()) { onEmptySpaceClick(); onTrackingStopped(true); @@ -5682,6 +5681,7 @@ public final class NotificationPanelViewController { /** @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 @@ public final class NotificationPanelViewController { // 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 @@ public final class NotificationPanelViewController { && 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 @@ public final class NotificationPanelViewController { 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 @@ public final class NotificationPanelViewController { && 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 65bd58d0d801..1e63b2dd134f 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 @@ public class NotificationShadeWindowViewController { return true; } - if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { + if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { // capture all touches if the alt auth bouncer is showing return true; } @@ -322,7 +322,7 @@ public class NotificationShadeWindowViewController { 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 084b7dc3a646..bf622c941abb 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 @@ class PulsingGestureListener @Inject constructor( 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 @@ class PulsingGestureListener @Inject constructor( } 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 f389dd970fbc..eaf7faecd5b3 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 @@ public class ShadeControllerImpl implements ShadeController { } 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 7f1bba350af1..761530101061 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 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { 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 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { "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/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 87ef92a28d5d..408293cffd99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -496,9 +496,6 @@ public class NotificationShelf extends ActivatableNotificationView implements return; } - final float smallCornerRadius = - getResources().getDimension(R.dimen.notification_corner_radius_small) - / getResources().getDimension(R.dimen.notification_corner_radius); final float viewEnd = viewStart + anv.getActualHeight(); final float cornerAnimationDistance = mCornerAnimationDistance * mAmbientState.getExpansionFraction(); @@ -509,7 +506,7 @@ public class NotificationShelf extends ActivatableNotificationView implements final float changeFraction = MathUtils.saturate( (viewEnd - cornerAnimationTop) / cornerAnimationDistance); anv.requestBottomRoundness( - anv.isLastInSection() ? 1f : changeFraction, + /* value = */ anv.isLastInSection() ? 1f : changeFraction, /* animate = */ false, SourceType.OnScroll); @@ -517,7 +514,7 @@ public class NotificationShelf extends ActivatableNotificationView implements // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. anv.requestBottomRoundness( - anv.isLastInSection() ? 1f : smallCornerRadius, + /* value = */ anv.isLastInSection() ? 1f : 0f, /* animate = */ false, SourceType.OnScroll); } @@ -527,16 +524,16 @@ public class NotificationShelf extends ActivatableNotificationView implements final float changeFraction = MathUtils.saturate( (viewStart - cornerAnimationTop) / cornerAnimationDistance); anv.requestTopRoundness( - anv.isFirstInSection() ? 1f : changeFraction, - false, + /* value = */ anv.isFirstInSection() ? 1f : changeFraction, + /* animate = */ false, SourceType.OnScroll); } else if (viewStart < cornerAnimationTop) { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. anv.requestTopRoundness( - anv.isFirstInSection() ? 1f : smallCornerRadius, - false, + /* value = */ anv.isFirstInSection() ? 1f : 0f, + /* animate = */ false, SourceType.OnScroll); } } @@ -976,6 +973,16 @@ public class NotificationShelf extends ActivatableNotificationView implements mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf); } + /** + * This method resets the OnScroll roundness of a view to 0f + * + * Note: This should be the only class that handles roundness {@code SourceType.OnScroll} + */ + public static void resetOnScrollRoundness(ExpandableView expandableView) { + expandableView.requestTopRoundness(0f, false, SourceType.OnScroll); + expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll); + } + public class ShelfState extends ExpandableViewState { private boolean hasItemsInStableShelf; private ExpandableView firstViewInShelf; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index 258f9fc5449f..c070fccf9808 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 @@ package com.android.systemui.statusbar; 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 java.util.concurrent.Executor; import javax.inject.Inject; /** + * */ @SysUISingleton public class VibratorHelper { @@ -40,9 +42,18 @@ public class VibratorHelper { 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 @@ public class VibratorHelper { } 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/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 26f0ad9eca87..0554fb5b3689 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -44,6 +44,7 @@ import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationGroupingUtil; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FeedbackIcon; import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -308,6 +309,11 @@ public class NotificationChildrenContainer extends ViewGroup row.setContentTransformationAmount(0, false /* isLastChild */); row.setNotificationFaded(mContainingNotificationIsFaded); + + // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness. + // Here we should reset the `OnScroll` roundness only on top-level rows. + NotificationShelf.resetOnScrollRoundness(row); + // It doesn't make sense to keep old animations around, lets cancel them! ExpandableViewState viewState = row.getViewState(); if (viewState != null) { @@ -1377,8 +1383,12 @@ public class NotificationChildrenContainer extends ViewGroup if (child.getVisibility() == View.GONE) { continue; } + child.requestTopRoundness( + /* value = */ 0f, + /* animate = */ isShown(), + SourceType.DefaultValue); child.requestBottomRoundness( - last ? getBottomRoundness() : 0f, + /* value = */ last ? getBottomRoundness() : 0f, /* animate = */ isShown(), SourceType.DefaultValue); last = false; 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 c4ef28e889ca..2c3330e12229 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.Assert; 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @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,10 +3708,32 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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) 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 4c52db7f8732..64dd6dcd3008 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 @@ package com.android.systemui.statusbar.notification.stack 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.NotificationStackScroll 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 @@ class NotificationStackScrollLogger @Inject constructor( "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 a2798f47be65..6e68079706f4 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.hardware.fingerprint.FingerprintManager; 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 com.android.systemui.statusbar.policy.KeyguardStateController; 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } // 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp /** 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 75b444fbd297..dc370821dc95 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 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void collapsePanelOnMainThread(); - void collapsePanelWithDuration(int duration); - void togglePanel(); void start(); @@ -305,9 +303,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void checkBarModes(); - // Called by NavigationBarFragment - void setQsScrimEnabled(boolean scrimEnabled); - void updateBubblesVisibility(); void setInteracting(int barWindow, boolean interacting); @@ -379,8 +374,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void showKeyguardImpl(); - boolean isInLaunchTransition(); - void fadeKeyguardAfterLaunchTransition(Runnable beforeFading, Runnable endRunnable, Runnable cancelRunnable); @@ -437,8 +430,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void showPinningEscapeToast(); - KeyguardBottomAreaView getKeyguardBottomAreaView(); - void setBouncerShowing(boolean bouncerShowing); void setBouncerShowingOverDream(boolean bouncerShowingOverDream); @@ -505,12 +496,8 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn 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 47a12a8b57a3..15cc086586da 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { initializer.initializeStatusBar(mCentralSurfacesComponent); mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView); - mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); createNavigationBar(result); @@ -1178,9 +1177,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mLockscreenWallpaper = mLockscreenWallpaperLazy.get(); } - mNotificationPanelViewController.setKeyguardIndicationController( - mKeyguardIndicationController); - mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById( R.id.ambient_indication_container); @@ -1531,11 +1527,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - /** 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 9bb4132490d4..b2a9509a03b7 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 @@ public class KeyguardBouncer { 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 @@ public class KeyguardBouncer { } /** + * 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/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index ccb5d8800ddb..a00e75642f55 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 com.android.systemui.unfold.SysUIUnfoldComponent; 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onExpansionChanged(float expansion) { - if (mAlternateAuthInterceptor != null) { - mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion); - } if (mBouncerAnimating) { mCentralSurfaces.setBouncerHiddenFraction(expansion); } - updateStates(); } @Override @@ -184,9 +182,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public boolean shouldDisableWindowAnimationsForUnlock() { - return mCentralSurfaces.isInLaunchTransition(); + return mNotificationPanelViewController.isLaunchTransitionFinished(); } @Override @@ -1356,7 +1352,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); } - if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) { + if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) { resetAlternateAuth(false); executeAfterKeyguardGoneAction(); } @@ -1442,6 +1438,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** + * 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 class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ public void setQsExpansion(float qsExpansion) { mQsExpansion = qsExpansion; - if (mAlternateAuthInterceptor != null) { - mAlternateAuthInterceptor.setQsExpansion(qsExpansion); + for (KeyguardViewManagerCallback callback : mCallbacks) { + callback.onQSExpansionChanged(mQsExpansion); } } @@ -1492,21 +1506,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb && 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,45 +1646,33 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 + * Use when an app occluding the keyguard would like to give the user ability to + * unlock the device using udfps. + * + * @param color of the udfps icon. should have proper contrast with its background. only + * used if requestUdfps = true */ - boolean onTouch(MotionEvent event); + void requestUdfps(boolean requestUdfps, int color); /** - * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully - * hidden + * print information for the alternate auth interceptor registered */ - void setBouncerExpansionChanged(float expansion); + void dump(PrintWriter pw); + } + /** + * Callback for KeyguardViewManager state changes. + */ + public interface KeyguardViewManagerCallback { /** - * called when the bouncer view visibility has changed. + * Set the amount qs is expanded. For example, swipe down from the top of the + * lock screen to start the full QS expansion. */ - void onBouncerVisibilityChanged(); + default void onQSExpansionChanged(float qsExpansion) { } /** - * Use when an app occluding the keyguard would like to give the user ability to - * unlock the device using udfps. - * - * @param color of the udfps icon. should have proper contrast with its background. only - * used if requestUdfps = true + * Forward touch events to callbacks */ - void requestUdfps(boolean requestUdfps, int color); - + 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 ee948c04df38..b1642d6addb6 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 @@ class StatusBarLaunchAnimatorController( delegate.onLaunchAnimationStart(isExpandingFullyAbove) centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true) if (!isExpandingFullyAbove) { - centralSurfaces.collapsePanelWithDuration( + centralSurfaces.notificationPanelViewController.collapseWithDuration( ActivityLaunchAnimator.TIMINGS.totalDuration.toInt()) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index aca60c033bac..131cf7d33e3a 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 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel( 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 52f8ef8b7ebc..0a2b3d8498c4 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 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @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 5104f84f3bc6..a8284d29197d 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.app.StatusBarManager.SESSION_KEYGUARD; 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.anyString; 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.IStrongAuthTracker; 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.PackageManager; 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.StatusBarState; 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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock + private SecureSettings mSecureSettings; + @Mock private TelephonyManager mTelephonyManager; @Mock private SensorPrivacyManager mSensorPrivacyManager; @@ -214,6 +223,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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 e8c760c3e140..d1107c612977 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.WindowInsets 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 @@ class AuthContainerViewTest : SysuiTestCase() { } @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 a02dfa3935d7..a275c8d33751 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.SysuiTestCase; 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 @@ public class AuthControllerTest extends SysuiTestCase { private DelayableExecutor mBackgroundExecutor; private TestableAuthController mAuthController; + @Mock + private VibratorHelper mVibratorHelper; + @Before public void setup() throws RemoteException { mContextSpy = spy(mContext); @@ -221,7 +225,8 @@ public class AuthControllerTest extends SysuiTestCase { 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 @@ public class AuthControllerTest extends SysuiTestCase { // 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 @@ public class AuthControllerTest extends SysuiTestCase { // 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 @@ public class AuthControllerTest extends SysuiTestCase { 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/AuthCredentialViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt new file mode 100644 index 000000000000..fbda08fab799 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt @@ -0,0 +1,163 @@ +/* + * 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.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING +import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.PromptInfo +import android.hardware.face.FaceSensorPropertiesInternal +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.os.Handler +import android.os.IBinder +import android.os.UserManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.ViewUtils +import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockPatternView +import com.android.internal.widget.VerifyCredentialResponse +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthContainerView.BiometricCallback +import com.android.systemui.biometrics.AuthCredentialView.ErrorTimer +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class AuthCredentialViewTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + @Mock lateinit var callback: AuthDialogCallback + @Mock lateinit var lockPatternUtils: LockPatternUtils + @Mock lateinit var userManager: UserManager + @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock lateinit var windowToken: IBinder + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + + private var authContainer: TestAuthContainerView? = null + private var authCredentialView: AuthCredentialPatternView? = null + private var lockPatternView: LockPatternView? = null + private var biometricCallback: BiometricCallback? = null + private var errorTimer: ErrorTimer? = null + + @After + fun tearDown() { + if (authContainer?.isAttachedToWindow == true) { + ViewUtils.detachView(authContainer) + } + } + + @Test + fun testAuthCredentialPatternView_onErrorTimeoutFinish_setPatternEnabled() { + `when`(lockPatternUtils.getCredentialTypeForUser(anyInt())) + .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PATTERN) + `when`(lockPatternUtils.getKeyguardStoredPasswordQuality(anyInt())) + .thenReturn(PASSWORD_QUALITY_SOMETHING) + val errorResponse: VerifyCredentialResponse = VerifyCredentialResponse.fromError() + + assertThat(initializeFingerprintContainer()).isNotNull() + authContainer?.animateToCredentialUI() + waitForIdleSync() + + authCredentialView = spy(authContainer?.mCredentialView as AuthCredentialPatternView) + authCredentialView?.onCredentialVerified(errorResponse, 5000) + errorTimer = authCredentialView?.mErrorTimer + errorTimer?.onFinish() + waitForIdleSync() + + verify(authCredentialView)?.onErrorTimeoutFinish() + + lockPatternView = authCredentialView?.mLockPatternView + assertThat(lockPatternView?.isEnabled).isTrue() + } + + private fun initializeFingerprintContainer( + authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, + addToView: Boolean = true + ) = + initializeContainer( + TestAuthContainerView( + authenticators = authenticators, + fingerprintProps = fingerprintSensorPropertiesInternal() + ), + addToView + ) + + private fun initializeContainer( + view: TestAuthContainerView, + addToView: Boolean + ): TestAuthContainerView { + authContainer = view + if (addToView) { + authContainer!!.addToView() + biometricCallback = authContainer?.mBiometricCallback + } + return authContainer!! + } + + private inner class TestAuthContainerView( + authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, + fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(), + faceProps: List<FaceSensorPropertiesInternal> = listOf() + ) : + AuthContainerView( + Config().apply { + mContext = context + mCallback = callback + mSensorIds = + (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId }) + .toIntArray() + mSkipAnimation = true + mPromptInfo = PromptInfo().apply { this.authenticators = authenticators } + }, + fingerprintProps, + faceProps, + wakefulnessLifecycle, + userManager, + lockPatternUtils, + interactionJankMonitor, + Handler(TestableLooper.get(this).looper), + FakeExecutor(FakeSystemClock()) + ) { + override fun postOnAnimation(runnable: Runnable) { + runnable.run() + } + } + + override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages() + + private fun AuthContainerView.addToView() { + ViewUtils.attachView(this) + waitForIdleSync() + assertThat(isAttachedToWindow).isTrue() + } +} 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 c85334db9499..90948ff3b769 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.R 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 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @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 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { 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 28e13b8e81ab..be39c0de22a1 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.R; 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 @@ public class UdfpsControllerTest extends SysuiTestCase { private FakeExecutor mFgExecutor; @Mock private UdfpsDisplayMode mUdfpsDisplayMode; + @Mock + private FeatureFlags mFeatureFlags; // Stuff for configuring mocks @Mock @@ -191,6 +195,8 @@ public class UdfpsControllerTest extends SysuiTestCase { 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 @@ public class UdfpsControllerTest extends SysuiTestCase { mStatusBarKeyguardViewManager, mDumpManager, mKeyguardUpdateMonitor, + mFeatureFlags, mFalsingManager, mPowerManager, mAccessibilityManager, @@ -270,7 +277,8 @@ public class UdfpsControllerTest extends SysuiTestCase { 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 000000000000..e5c7a42c06a6 --- /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 c0f9c82fb131..55b61948ee45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -25,125 +25,52 @@ import static org.mockito.Mockito.atLeastOnce; 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 org.junit.Before; +import com.android.systemui.statusbar.phone.KeyguardBouncer; + 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(); - - 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); +public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest { + private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> + mBouncerExpansionCallbackCaptor; + private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback; + + @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(); @@ -202,20 +129,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { } @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 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { 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 000000000000..7b1976811868 --- /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/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index b9ab9d37a805..53d9b87b2346 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.SysuiTestCase 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 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @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 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { keyguardStateController, dozeHost, wakefulnessLifecycle, + biometricUnlockController, ) } @@ -190,7 +194,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun wakefullness() = runBlockingTest { + fun wakefulness() = runBlockingTest { val values = mutableListOf<WakefulnessModel>() val job = underTest.wakefulnessState.onEach(values::add).launchIn(this) @@ -217,4 +221,63 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { 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 64913c7c28c2..27d5d0a98978 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 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { 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 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } 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 e6c8dd87d982..5743b2f03d3a 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.SysuiTestCase 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 @@ class BouncerInteractorTest : SysuiTestCase() { @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 @@ class BouncerInteractorTest : SysuiTestCase() { ) `when`(repository.startingDisappearAnimation.value).thenReturn(null) `when`(repository.show.value).thenReturn(null) + `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate) } @Test @@ -97,7 +99,7 @@ class BouncerInteractorTest : SysuiTestCase() { 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 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun testShow_isNotScrimmed() { - verify(repository, never()).setExpansion(EXPANSION_VISIBLE) + verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE) } @Test @@ -124,7 +126,6 @@ class BouncerInteractorTest : SysuiTestCase() { 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 @@ class BouncerInteractorTest : SysuiTestCase() { @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 @@ class BouncerInteractorTest : SysuiTestCase() { @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 @@ class BouncerInteractorTest : SysuiTestCase() { 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 @@ class BouncerInteractorTest : SysuiTestCase() { @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 @@ class BouncerInteractorTest : SysuiTestCase() { 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 @@ class BouncerInteractorTest : SysuiTestCase() { @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/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 0424c28a1c24..6333b244fd38 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 6adce7a827b6..c1fa9b39f50b 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.Display; 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 @@ public class NavigationBarTest extends SysuiTestCase { 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 @@ public class NavigationBarTest extends SysuiTestCase { 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 2c76be64aa7c..b067ee76f3b3 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.never; 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 @@ public class QSIconViewImplTest extends SysuiTestCase { 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 45b4353d0ec0..c98c1f2bc97e 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 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mSysUiState, () -> mKeyguardBottomAreaViewController, mKeyguardUnlockAnimationController, + mKeyguardIndicationController, mNotificationListContainer, mNotificationStackSizeCalculator, mUnlockedScreenOffAnimationController, @@ -499,8 +500,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { () -> {}, 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 26a0770a7bba..a4a7995ae3c8 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 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // 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 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // 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 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // 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 09add652483e..43c694245eba 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 @@ class PulsingGestureListenerTest : SysuiTestCase() { 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 @@ class PulsingGestureListenerTest : SysuiTestCase() { centralSurfaces, ambientDisplayConfiguration, statusBarStateController, + shadeLogger, tunerService, dumpManager ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 421f918a135e..7478e4c1f878 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -22,6 +22,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -60,6 +61,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -194,6 +196,25 @@ public class NotificationTestHelper { } /** + * Creates a generic row with rounded border. + * + * @return a generic row with the set roundness. + * @throws Exception + */ + public ExpandableNotificationRow createRowWithRoundness( + float topRoundness, + float bottomRoundness, + SourceType sourceType + ) throws Exception { + ExpandableNotificationRow row = createRow(); + row.requestTopRoundness(topRoundness, false, sourceType); + row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType); + assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f); + assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f); + return row; + } + + /** * Creates a generic row. * * @return a generic row with no special properties. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index 7c41abba6176..438b528944be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -25,6 +25,7 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -151,4 +152,37 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { Assert.assertNotNull("Children container must have a header after recreation", mChildrenContainer.getCurrentHeaderView()); } + + @Test + public void addNotification_shouldResetOnScrollRoundness() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnScroll); + + mChildrenContainer.addNotification(row, 0); + + Assert.assertEquals(0f, row.getTopRoundness(), /* delta = */ 0f); + Assert.assertEquals(0f, row.getBottomRoundness(), /* delta = */ 0f); + } + + @Test + public void addNotification_shouldNotResetOtherRoundness() throws Exception { + ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.DefaultValue); + ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnDismissAnimation); + + mChildrenContainer.addNotification(row1, 0); + mChildrenContainer.addNotification(row2, 0); + + Assert.assertEquals(1f, row1.getTopRoundness(), /* delta = */ 0f); + Assert.assertEquals(1f, row1.getBottomRoundness(), /* delta = */ 0f); + Assert.assertEquals(1f, row2.getTopRoundness(), /* delta = */ 0f); + Assert.assertEquals(1f, row2.getBottomRoundness(), /* delta = */ 0f); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 77418138158b..bda233611158 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -1,6 +1,7 @@ package com.android.systemui.statusbar.notification.stack import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress @@ -8,8 +9,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.SourceType import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals @@ -37,6 +40,13 @@ class NotificationShelfTest : SysuiTestCase() { private val shelfState = shelf.viewState as NotificationShelf.ShelfState private val ambientState = mock(AmbientState::class.java) private val hostLayoutController: NotificationStackScrollLayoutController = mock() + private val notificationTestHelper by lazy { + allowTestableLooperAsMainThread() + NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)) + } @Before fun setUp() { @@ -299,6 +309,39 @@ class NotificationShelfTest : SysuiTestCase() { ) } + @Test + fun resetOnScrollRoundness_shouldSetOnScrollTo0() { + val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnScroll) + + NotificationShelf.resetOnScrollRoundness(row) + + assertEquals(0f, row.topRoundness) + assertEquals(0f, row.bottomRoundness) + } + + @Test + fun resetOnScrollRoundness_shouldNotResetOtherRoundness() { + val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.DefaultValue) + val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnDismissAnimation) + + NotificationShelf.resetOnScrollRoundness(row1) + NotificationShelf.resetOnScrollRoundness(row2) + + assertEquals(1f, row1.topRoundness) + assertEquals(1f, row1.bottomRoundness) + assertEquals(1f, row2.topRoundness) + assertEquals(1f, row2.bottomRoundness) + } + private fun setFractionToShade(fraction: Float) { whenever(ambientState.fractionToShade).thenReturn(fraction) } 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 6fa217415044..b17747ad2d92 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 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { 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 57557821cca6..7ce3a67ce835 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { 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/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 0c35659b458a..716666657871 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @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/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 11178db8afd2..627bd096143e 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 @@ class FakeKeyguardRepository : KeyguardRepository { 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/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 252dcfc5eb9e..4430bb4b3292 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 static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 df5113b16f49..85d6f293852c 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 @@ public class ActivityManagerService extends IActivityManager.Stub || 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 ca4b7475f7dc..dbe971f32dc1 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 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> 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 9baca98e4d2b..91eec7d19b3a 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 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { 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 a778b573f225..9aecf783a881 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 @@ import java.util.function.Supplier; 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 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> taskStackListener, lockoutCache, allowBackgroundAuthentication, - true /* shouldVibrate */, + false /* shouldVibrate */, false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutCache = lockoutCache; @@ -235,12 +234,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> () -> { 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 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> 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 7ed1a514f9bc..0d620fd3a9e4 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 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi 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 5589673973c3..e4aa5e574955 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 @@ public final class DreamManagerService extends SystemService { 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 26ca74a8d808..96ebcbb6a771 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 @@ import com.android.internal.util.FrameworkStatsLog; * @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 @@ public class DreamUiEventLoggerImpl implements DreamUiEventLogger { 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 2eb2cf643c42..e86f34bb5d84 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A StartingData mStartingData; WindowState mStartingWindow; StartingSurfaceController.StartingSurface mStartingSurface; - boolean startingDisplayed; boolean startingMoved; /** The last set {@link DropInputMode} for this activity surface. */ @@ -821,13 +820,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * @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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 9c95e31cc5f5..671524b39bc1 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 @@ public class AppTransitionController { 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 e7ab63eab202..13a1cb6daf38 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 @@ class Dimmer { 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 @@ class Dimmer { } /** - * 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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 42a3ec6abbc5..2688ff757f64 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -81,11 +81,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -1439,90 +1435,6 @@ public class DisplayPolicy { */ int selectAnimation(WindowState win, int transit) { ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit); - if (win == mStatusBar) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - return R.anim.dock_top_exit; - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_top_enter; - } - } else if (win == mNavigationBar) { - if (win.getAttrs().windowAnimations != 0) { - return ANIMATION_STYLEABLE; - } - // This can be on either the bottom or the right or the left. - if (mNavigationBarPosition == NAV_BAR_BOTTOM) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) { - return R.anim.dock_bottom_exit_keyguard; - } else { - return R.anim.dock_bottom_exit; - } - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_bottom_enter; - } - } else if (mNavigationBarPosition == NAV_BAR_RIGHT) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - return R.anim.dock_right_exit; - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_right_enter; - } - } else if (mNavigationBarPosition == NAV_BAR_LEFT) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - return R.anim.dock_left_exit; - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_left_enter; - } - } - } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt - || win == mExtraNavBarAlt) { - if (win.getAttrs().windowAnimations != 0) { - return ANIMATION_STYLEABLE; - } - - int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition; - - boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE; - boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW; - - switch (pos) { - case ALT_BAR_LEFT: - if (isExitOrHide) { - return R.anim.dock_left_exit; - } else if (isEnterOrShow) { - return R.anim.dock_left_enter; - } - break; - case ALT_BAR_RIGHT: - if (isExitOrHide) { - return R.anim.dock_right_exit; - } else if (isEnterOrShow) { - return R.anim.dock_right_enter; - } - break; - case ALT_BAR_BOTTOM: - if (isExitOrHide) { - return R.anim.dock_bottom_exit; - } else if (isEnterOrShow) { - return R.anim.dock_bottom_enter; - } - break; - case ALT_BAR_TOP: - if (isExitOrHide) { - return R.anim.dock_top_exit; - } else if (isEnterOrShow) { - return R.anim.dock_top_enter; - } - break; - } - } if (transit == TRANSIT_PREVIEW_DONE) { if (win.hasAppShownWindows()) { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index a8d13c57ffa2..eaa08fd5eb0b 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -1578,7 +1578,9 @@ public class DisplayRotation { false /* forceRelayout */); } else { // Revert the rotation to our saved value if we transition from HALF_FOLDED. - mRotation = mHalfFoldSavedRotation; + if (mHalfFoldSavedRotation != -1) { + mRotation = mHalfFoldSavedRotation; + } // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here // so we will override USER_ROTATION_LOCKED and allow a rotation). mService.updateRotation(false /* alwaysSendConfiguration */, diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index f11c2a7da840..dcb7fe3fbc8b 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 @@ public class LockTaskController { 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 @@ public class LockTaskController { 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 @@ public class LockTaskController { // 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 fbee343f09b5..300a894d15c9 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 @@ public abstract class StartingData { */ 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 eba49bbc7301..66d7af9b4f6c 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 @@ class Task extends TaskFragment { 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 @@ class Task extends TaskFragment { 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 d17867673474..879c7e2d75dc 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 ac720be90563..9c9d751244e6 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 744bf0aa42d9..d4c1abfa8d24 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * @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 5c0557f2d1f4..a0ba8fda906f 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 @@ class WindowStateAnimator { } void destroySurfaceLocked(SurfaceControl.Transaction t) { - final ActivityRecord activity = mWin.mActivityRecord; - if (activity != null) { - if (mWin == activity.mStartingWindow) { - activity.startingDisplayed = false; - } - } - if (mSurfaceController == null) { return; } @@ -602,11 +595,17 @@ class WindowStateAnimator { return true; } - final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD; - if (isEntrance && isImeWindow) { + if (mWin.mAttrs.type == TYPE_INPUT_METHOD) { mWin.getDisplayContent().adjustForImeIfNeeded(); - mWin.setDisplayLayoutNeeded(); - mService.mWindowPlacerLocked.requestTraversal(); + if (isEntrance) { + mWin.setDisplayLayoutNeeded(); + mService.mWindowPlacerLocked.requestTraversal(); + } + } + + if (mWin.mControllableInsetProvider != null) { + // All our animations should be driven by the insets control target. + return false; } // Only apply an animation if the display isn't frozen. If it is @@ -654,14 +653,10 @@ class WindowStateAnimator { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); mAnimationIsEntrance = isEntrance; } - } else if (!isImeWindow) { + } else { mWin.cancelAnimation(); } - if (!isEntrance && isImeWindow) { - mWin.getDisplayContent().adjustForImeIfNeeded(); - } - return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION); } 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 7610b7ca5ec3..b33e22fe6d1e 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 class AppWidgetServiceImplTest extends InstrumentationTestCase { } 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 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { 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 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { } 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 fd99b21c9538..000000000000 --- 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 000000000000..6c11a6829c12 --- /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 606f4864643c..cd4af0a581f8 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 @@ public class FingerprintAuthenticationClientTest { 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 462957a88a6c..8a0a4f7c82b7 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 @@ public class ActivityRecordTests extends WindowTestsBase { 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 @@ public class ActivityRecordTests extends WindowTestsBase { 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 @@ public class ActivityRecordTests extends WindowTestsBase { activity1.onFirstWindowDrawn(activityWindow); activity2.onFirstWindowDrawn(activityWindow); assertNull(activity1.mStartingWindow); + assertNull(task.mSharedStartingData); } @Test @@ -2991,10 +2992,10 @@ public class ActivityRecordTests extends WindowTestsBase { 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 513791d2b8a5..0332c4b4597b 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 @@ public class AppTransitionControllerTest extends WindowTestsBase { 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 f61effa284ef..d55e53cca8a6 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 @@ public class AppTransitionTests extends WindowTestsBase { 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 55a7c1ba0bca..befe4e85a7de 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.times; 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 @@ public class DimmerTests extends WindowTestsBase { } @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 @@ public class DimmerTests extends WindowTestsBase { } @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 352d8d115b6a..bc5c9ec7a29c 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { } } + @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 b9793cafb6fe..fabab2524e83 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 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne 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 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } 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 ae9be8c09e60..b24337fd54c9 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_DATA; 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.IVoiceInteractionSessionService; 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, public void cancelLocked(boolean finishTask) { mListeningVisibleActivity = false; - mVisibleActivityInfos.clear(); + mVisibleActivityInfoForToken.clear(); hideLocked(); mCanceled = true; if (mBound) { @@ -608,17 +614,24 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, if (DEBUG) { Slog.d(TAG, "startListeningVisibleActivityChangedLocked"); } + + if (!mShown || mCanceled || mSession == null) { + return; + } + mListeningVisibleActivity = true; - mVisibleActivityInfos.clear(); + mVisibleActivityInfoForToken.clear(); - mScheduledExecutorService.execute(() -> { - if (DEBUG) { - Slog.d(TAG, "call handleVisibleActivitiesLocked from enable listening"); - } - synchronized (mLock) { - handleVisibleActivitiesLocked(); - } - }); + // 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, 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) { + + if (!mListeningVisibleActivity) { + if (DEBUG) { + Slog.d(TAG, "not enable listening visible activity"); + } return; } - if (!mShown || !mListeningVisibleActivity || mCanceled) { + if (!mShown || mCanceled || mSession == null) { return; } - final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked(); - if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) { - notifyVisibleActivitiesChangedLocked(mVisibleActivityInfos, - VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); - mVisibleActivityInfos.clear(); - return; - } - if (mVisibleActivityInfos.isEmpty()) { - notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos, - VisibleActivityInfo.TYPE_ACTIVITY_ADDED); - mVisibleActivityInfos.addAll(newVisibleActivityInfos); + // We use this local variable to determine to call onVisible or onInvisible. + boolean notifyOnVisible = false; + VisibleActivityInfo notifyVisibleActivityInfo = null; + + 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; } - final List<VisibleActivityInfo> addedActivities = new ArrayList<>(); - final List<VisibleActivityInfo> removedActivities = new ArrayList<>(); - - 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); + 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 (!removedActivities.isEmpty()) { - notifyVisibleActivitiesChangedLocked(removedActivities, - VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + if (notifyOnVisible) { + mVisibleActivityInfoForToken.put(activityToken, notifyVisibleActivityInfo); + } else { + mVisibleActivityInfoForToken.remove(activityToken); } - - 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } } + 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) { |